mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-28 02:18:50 +00:00
feat(portal): Zero-click client authentication (#9144)
Adds a new field to `settings/identity_providers` that allows an Admin to designate any non-email/otp provider as the `default` for client authentication. Clients will then navigate directly to the provider's `/redirect` endpoint when authenticating, which in many cases will automatically sign them in. No existing providers are updated in this PR. https://github.com/user-attachments/assets/7b962a25-76fd-491f-a194-60ed993821fc
This commit is contained in:
@@ -152,6 +152,14 @@ defmodule Domain.Auth do
|
||||
end
|
||||
end
|
||||
|
||||
# Used during client auth
|
||||
def fetch_default_provider_for_account(%Accounts.Account{} = account, opts \\ []) do
|
||||
Provider.Query.not_disabled()
|
||||
|> Provider.Query.by_account_id(account.id)
|
||||
|> Provider.Query.assigned_default()
|
||||
|> Repo.fetch(Provider.Query, opts)
|
||||
end
|
||||
|
||||
def list_providers(%Subject{} = subject, opts \\ []) do
|
||||
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
|
||||
Provider.Query.not_deleted()
|
||||
@@ -252,6 +260,38 @@ defmodule Domain.Auth do
|
||||
end)
|
||||
end
|
||||
|
||||
# Update default provider for client auth
|
||||
def assign_default_provider(%Provider{} = provider, %Subject{} = subject) do
|
||||
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
|
||||
Repo.transaction(fn ->
|
||||
# 1. Clear default for all other providers
|
||||
{_count, nil} =
|
||||
Provider.Query.not_disabled()
|
||||
|> Authorizer.for_subject(Provider, subject)
|
||||
|> Repo.update_all(set: [assigned_default_at: nil])
|
||||
|
||||
# 2. Set default for the given provider
|
||||
{:ok, provider} =
|
||||
Provider.Query.not_disabled()
|
||||
|> Provider.Query.by_id(provider.id)
|
||||
|> Authorizer.for_subject(Provider, subject)
|
||||
|> Repo.fetch_and_update(Provider.Query,
|
||||
with: &Provider.Changeset.assign_default_provider/1
|
||||
)
|
||||
|
||||
provider
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def clear_default_provider(%Subject{} = subject) do
|
||||
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
|
||||
Provider.Query.not_disabled()
|
||||
|> Authorizer.for_subject(Provider, subject)
|
||||
|> Repo.update_all(set: [assigned_default_at: nil])
|
||||
end
|
||||
end
|
||||
|
||||
def enable_provider(%Provider{} = provider, %Subject{} = subject) do
|
||||
mutate_provider(provider, subject, &Provider.Changeset.enable_provider/1)
|
||||
end
|
||||
|
||||
@@ -28,6 +28,7 @@ defmodule Domain.Auth.Provider do
|
||||
|
||||
field :disabled_at, :utc_datetime_usec
|
||||
field :deleted_at, :utc_datetime_usec
|
||||
field :assigned_default_at, :utc_datetime_usec
|
||||
timestamps()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Domain.Auth.Provider.Changeset do
|
||||
alias Domain.Accounts
|
||||
alias Domain.Auth.{Subject, Provider, Adapters}
|
||||
|
||||
@create_fields ~w[id name adapter provisioner adapter_config adapter_state disabled_at]a
|
||||
@create_fields ~w[id name adapter provisioner adapter_config adapter_state disabled_at assigned_default_at]a
|
||||
@update_fields ~w[name adapter_config
|
||||
last_syncs_failed last_sync_error sync_disabled_at sync_error_emailed_at
|
||||
adapter_state provisioner disabled_at deleted_at]a
|
||||
@@ -43,6 +43,12 @@ defmodule Domain.Auth.Provider.Changeset do
|
||||
|> changeset()
|
||||
end
|
||||
|
||||
def assign_default_provider(%Provider{} = provider) do
|
||||
provider
|
||||
|> change()
|
||||
|> put_change(:assigned_default_at, DateTime.utc_now())
|
||||
end
|
||||
|
||||
def sync_finished(%Provider{} = provider) do
|
||||
provider
|
||||
|> change()
|
||||
@@ -89,6 +95,10 @@ defmodule Domain.Auth.Provider.Changeset do
|
||||
name: :unique_account_adapter_index,
|
||||
message: "only one of this adapter type may be enabled per account"
|
||||
)
|
||||
|> unique_constraint(:base,
|
||||
name: :auth_providers_account_id_assigned_default_at_index,
|
||||
message: "only one provider may be assigned default for client authentication"
|
||||
)
|
||||
|> validate_provisioner()
|
||||
|> validate_required(@required_fields)
|
||||
end
|
||||
|
||||
@@ -14,6 +14,10 @@ defmodule Domain.Auth.Provider.Query do
|
||||
where(queryable, [providers: providers], is_nil(providers.disabled_at))
|
||||
end
|
||||
|
||||
def assigned_default(queryable) do
|
||||
where(queryable, [providers: providers], not is_nil(providers.assigned_default_at))
|
||||
end
|
||||
|
||||
def by_id(queryable, id)
|
||||
|
||||
def by_id(queryable, {:not, id}) do
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
defmodule Domain.Repo.Migrations.AddDefaultToAuthProviders do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
|
||||
def up do
|
||||
alter table(:auth_providers) do
|
||||
add(:assigned_default_at, :utc_datetime_usec)
|
||||
end
|
||||
|
||||
create(
|
||||
index(:auth_providers, :account_id,
|
||||
name: :auth_providers_account_id_assigned_default_at_index,
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL AND disabled_at IS NULL AND assigned_default_at IS NOT NULL",
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def down do
|
||||
drop(
|
||||
index(:auth_providers, :account_id,
|
||||
name: :auth_providers_account_id_assigned_default_at_index,
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL AND disabled_at IS NULL AND assigned_default_at IS NOT NULL",
|
||||
concurrently: true
|
||||
)
|
||||
)
|
||||
|
||||
alter table(:auth_providers) do
|
||||
remove(:assigned_default_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -217,6 +217,117 @@ defmodule Domain.AuthTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetch_default_provider_for_account/2" do
|
||||
test "returns not found if no default providers exist" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
assert fetch_default_provider_for_account(account) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "returns default provider for account" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
{provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(
|
||||
account: account,
|
||||
assigned_default_at: DateTime.utc_now()
|
||||
)
|
||||
|
||||
assert {:ok, fetched_provider} = fetch_default_provider_for_account(account)
|
||||
assert fetched_provider.id == provider.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "assign_default_provider/2" do
|
||||
setup do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
actor = Fixtures.Actors.create_actor(account: account, type: :account_admin_user)
|
||||
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
|
||||
subject = Fixtures.Auth.create_subject(identity: identity)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
subject: subject
|
||||
}
|
||||
end
|
||||
|
||||
test "assigns default provider for account", %{account: account, subject: subject} do
|
||||
{provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
assert {:ok, provider} = assign_default_provider(provider, subject)
|
||||
assert provider.assigned_default_at
|
||||
end
|
||||
|
||||
test "clears default from all other providers in same account", %{
|
||||
account: account,
|
||||
subject: subject
|
||||
} do
|
||||
{provider1, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(
|
||||
account: account,
|
||||
assigned_default_at: DateTime.utc_now()
|
||||
)
|
||||
|
||||
{provider2, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
assert {:ok, provider} = assign_default_provider(provider2, subject)
|
||||
assert provider.assigned_default_at
|
||||
|
||||
assert provider1 = Repo.reload(provider1)
|
||||
assert is_nil(provider1.assigned_default_at)
|
||||
end
|
||||
|
||||
test "prevents clearing default from other accounts' providers", %{
|
||||
subject: subject
|
||||
} do
|
||||
other_account = Fixtures.Accounts.create_account()
|
||||
|
||||
{other_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(
|
||||
account: other_account,
|
||||
assigned_default_at: DateTime.utc_now()
|
||||
)
|
||||
|
||||
assert_raise MatchError, fn ->
|
||||
assign_default_provider(other_provider, subject)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "clear_default_provider/1" do
|
||||
setup do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
actor = Fixtures.Actors.create_actor(account: account, type: :account_admin_user)
|
||||
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
|
||||
subject = Fixtures.Auth.create_subject(identity: identity)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
subject: subject
|
||||
}
|
||||
end
|
||||
|
||||
test "clears default provider from account", %{account: account, subject: subject} do
|
||||
{provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(
|
||||
account: account,
|
||||
assigned_default_at: DateTime.utc_now()
|
||||
)
|
||||
|
||||
assert {:ok, default_provider} = fetch_default_provider_for_account(account)
|
||||
assert provider.id == default_provider.id
|
||||
|
||||
assert {_count, nil} = clear_default_provider(subject)
|
||||
provider = Repo.reload(provider)
|
||||
assert is_nil(provider.assigned_default_at)
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_providers/2" do
|
||||
test "returns all not soft-deleted providers for a given account" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
@@ -234,7 +234,7 @@ defmodule Web.FormComponents do
|
||||
class={[
|
||||
"text-sm bg-neutral-50",
|
||||
"border border-neutral-300 text-neutral-900 rounded",
|
||||
"block p-2.5",
|
||||
"block",
|
||||
!@inline_errors && "w-full",
|
||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
||||
]}
|
||||
|
||||
@@ -368,4 +368,18 @@ defmodule Web.Settings.IdentityProviders.Components do
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :provider, Domain.Auth.Provider, required: true
|
||||
|
||||
def assigned_default_badge(assigns) do
|
||||
~H"""
|
||||
<.badge
|
||||
:if={!is_nil(@provider.assigned_default_at)}
|
||||
title="This provider is the default for client authentication"
|
||||
class="ml-2"
|
||||
>
|
||||
default
|
||||
</.badge>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,7 +154,10 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Status</:label>
|
||||
|
||||
@@ -2,6 +2,7 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
use Web, :live_view
|
||||
import Web.Settings.IdentityProviders.Components
|
||||
alias Domain.{Auth, Actors}
|
||||
require Logger
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
with {:ok, identities_count_by_provider_id} <-
|
||||
@@ -11,6 +12,7 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
socket =
|
||||
socket
|
||||
|> assign(
|
||||
default_provider_changed: false,
|
||||
page_title: "Identity Providers",
|
||||
identities_count_by_provider_id: identities_count_by_provider_id,
|
||||
groups_count_by_provider_id: groups_count_by_provider_id
|
||||
@@ -69,6 +71,19 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
<:content>
|
||||
<.flash_group flash={@flash} />
|
||||
|
||||
<div class="pb-8 px-1">
|
||||
<div class="text-lg text-neutral-600 mb-4">
|
||||
Default Authentication Provider
|
||||
</div>
|
||||
<.default_provider_form
|
||||
providers={@providers}
|
||||
default_provider_changed={@default_provider_changed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-lg text-neutral-600 mb-4 px-1">
|
||||
All identity providers
|
||||
</div>
|
||||
<.live_table
|
||||
id="providers"
|
||||
rows={@providers}
|
||||
@@ -82,6 +97,7 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
<.link navigate={view_provider(@account, provider)} class={[link_style()]}>
|
||||
{provider.name}
|
||||
</.link>
|
||||
<.assigned_default_badge provider={provider} />
|
||||
</:col>
|
||||
<:col :let={provider} label="Type" class="w-2/12">
|
||||
{adapter_name(provider.adapter)}
|
||||
@@ -115,6 +131,128 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
"""
|
||||
end
|
||||
|
||||
attr :providers, :list, required: true
|
||||
attr :default_provider_changed, :boolean, required: true
|
||||
|
||||
defp default_provider_form(assigns) do
|
||||
options =
|
||||
assigns.providers
|
||||
|> Enum.filter(fn provider ->
|
||||
provider.adapter not in [:email, :userpass]
|
||||
end)
|
||||
|> Enum.map(fn provider ->
|
||||
{provider.name, provider.id}
|
||||
end)
|
||||
|
||||
options = [{"None", :none} | options]
|
||||
|
||||
value =
|
||||
assigns.providers
|
||||
|> Enum.find(%{id: :none}, fn provider ->
|
||||
!is_nil(provider.assigned_default_at)
|
||||
end)
|
||||
|> Map.get(:id)
|
||||
|
||||
assigns = assign(assigns, options: options, value: value)
|
||||
|
||||
~H"""
|
||||
<.form
|
||||
id="default-provider-form"
|
||||
phx-submit="default_provider_save"
|
||||
phx-change="default_provider_change"
|
||||
for={nil}
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="w-32">
|
||||
<.input
|
||||
id="default-provider-select"
|
||||
name="provider_id"
|
||||
type="select"
|
||||
options={@options}
|
||||
value={@value}
|
||||
/>
|
||||
</div>
|
||||
<.submit_button
|
||||
phx-disable-with="Saving..."
|
||||
{if @default_provider_changed, do: [], else: [disabled: true, style: "disabled"]}
|
||||
icon="hero-identification"
|
||||
>
|
||||
Make Default
|
||||
</.submit_button>
|
||||
</div>
|
||||
<p class="text-xs text-neutral-500 mt-2">
|
||||
When selected, users signing in from the Firezone client will be taken directly to this provider for authentication.
|
||||
</p>
|
||||
</.form>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("default_provider_change", _params, socket) do
|
||||
{:noreply, assign(socket, default_provider_changed: true)}
|
||||
end
|
||||
|
||||
def handle_event("default_provider_save", %{"provider_id" => provider_id}, socket) do
|
||||
assign_default_provider(provider_id, socket)
|
||||
end
|
||||
|
||||
def handle_event(event, params, socket) when event in ["paginate", "order_by", "filter"],
|
||||
do: handle_live_table_event(event, params, socket)
|
||||
|
||||
# Clear default provider
|
||||
defp assign_default_provider("none", socket) do
|
||||
with {_count, nil} <- Auth.clear_default_provider(socket.assigns.subject),
|
||||
{:ok, providers, _metadata} <- Auth.list_providers(socket.assigns.subject) do
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(:info, "Default authentication provider cleared")
|
||||
|> assign(default_provider_changed: false, providers: providers)
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
error ->
|
||||
Logger.warning("Failed to clear default auth provider",
|
||||
error: inspect(error)
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Failed to update default auth provider. Contact support if this issue persists."
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp assign_default_provider(provider_id, socket) do
|
||||
provider =
|
||||
socket.assigns.providers
|
||||
|> Enum.find(fn provider -> provider.id == provider_id end)
|
||||
|
||||
with true <- provider.adapter not in [:email, :userpass],
|
||||
{:ok, _provider} <- Auth.assign_default_provider(provider, socket.assigns.subject),
|
||||
{:ok, providers, _metadata} <- Auth.list_providers(socket.assigns.subject) do
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(:info, "Default authentication provider set to #{provider.name}")
|
||||
|> assign(default_provider_changed: false, providers: providers)
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
error ->
|
||||
Logger.warning("Failed to set default auth provider",
|
||||
error: inspect(error)
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Failed to update default auth provider. Contact support if this issue persists."
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,7 +154,10 @@ defmodule Web.Settings.IdentityProviders.JumpCloud.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Status</:label>
|
||||
|
||||
@@ -152,7 +152,10 @@ defmodule Web.Settings.IdentityProviders.MicrosoftEntra.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Status</:label>
|
||||
|
||||
@@ -141,7 +141,10 @@ defmodule Web.Settings.IdentityProviders.Mock.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Description</:label>
|
||||
|
||||
@@ -152,7 +152,10 @@ defmodule Web.Settings.IdentityProviders.Okta.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Status</:label>
|
||||
|
||||
@@ -132,7 +132,10 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
|
||||
<.vertical_table id="provider">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value>{@provider.name}</:value>
|
||||
<:value>
|
||||
{@provider.name}
|
||||
<.assigned_default_badge provider={@provider} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Status</:label>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
defmodule Web.Plugs.AutoRedirectDefaultProvider do
|
||||
use Phoenix.VerifiedRoutes, endpoint: Web.Endpoint, router: Web.Router
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [redirect: 2]
|
||||
alias Domain.Auth
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
# client sign in
|
||||
def call(%{params: %{"as" => "client"}} = conn, _opts) do
|
||||
with account <- conn.assigns.account,
|
||||
{:ok, provider} <- Auth.fetch_default_provider_for_account(account) do
|
||||
redirect_path = ~p"/#{account}/sign_in/providers/#{provider}/redirect"
|
||||
|
||||
# Append original query params
|
||||
full_redirect_path =
|
||||
if conn.query_string != "" do
|
||||
redirect_path <> "?" <> conn.query_string
|
||||
else
|
||||
redirect_path
|
||||
end
|
||||
|
||||
conn
|
||||
|> redirect(to: full_redirect_path)
|
||||
|> halt()
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
# Non-client sign in
|
||||
def call(conn, _opts) do
|
||||
conn
|
||||
end
|
||||
end
|
||||
@@ -74,7 +74,12 @@ defmodule Web.Router do
|
||||
end
|
||||
|
||||
scope "/:account_id_or_slug", Web do
|
||||
pipe_through [:public, :account, :redirect_if_user_is_authenticated]
|
||||
pipe_through [
|
||||
:public,
|
||||
:account,
|
||||
:redirect_if_user_is_authenticated,
|
||||
Web.Plugs.AutoRedirectDefaultProvider
|
||||
]
|
||||
|
||||
live_session :redirect_if_user_is_authenticated,
|
||||
on_mount: [
|
||||
|
||||
@@ -63,6 +63,80 @@ defmodule Web.Live.Settings.IdentityProviders.IndexTest do
|
||||
assert Floki.text(button) =~ "Add Identity Provider"
|
||||
end
|
||||
|
||||
test "renders default provider form", %{account: account, identity: identity, conn: conn} do
|
||||
{:ok, _lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/settings/identity_providers")
|
||||
|
||||
assert Floki.text(html) =~ "Default Authentication Provider"
|
||||
assert form = Floki.find(html, "form#default-provider-form")
|
||||
|
||||
assert Floki.text(form) =~
|
||||
"When selected, users signing in from the Firezone client will be taken directly to this provider for authentication."
|
||||
end
|
||||
|
||||
test "allows setting a default provider", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{provider, _bypass} = Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
{:ok, lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/settings/identity_providers")
|
||||
|
||||
assert Floki.text(html) =~ "Default Authentication Provider"
|
||||
|
||||
html =
|
||||
lv
|
||||
|> form("#default-provider-form", %{
|
||||
"provider_id" => provider.id
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
# Assert the default provider is set
|
||||
assert html
|
||||
|> Floki.find("option[selected]")
|
||||
|> Floki.attribute("value") == [to_string(provider.id)]
|
||||
end
|
||||
|
||||
test "allows clearing the default provider", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(
|
||||
account: account,
|
||||
assigned_default_at: DateTime.utc_now()
|
||||
)
|
||||
|
||||
{:ok, lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/settings/identity_providers")
|
||||
|
||||
assert Floki.text(html) =~ "Default Authentication Provider"
|
||||
|
||||
html =
|
||||
lv
|
||||
|> form("#default-provider-form", %{
|
||||
"provider_id" => "none"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
# Assert the default provider is set
|
||||
assert html
|
||||
|> Floki.find("option[selected]")
|
||||
|> Floki.attribute("value") == ["none"]
|
||||
|
||||
provider = Repo.reload(provider)
|
||||
assert is_nil(provider.assigned_default_at)
|
||||
end
|
||||
|
||||
test "renders table with multiple providers", %{
|
||||
account: account,
|
||||
openid_connect_provider: openid_connect_provider,
|
||||
|
||||
Reference in New Issue
Block a user