mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Add sign-in success page for clients (#3714)
Why: * On some clients, the web view that is opened to sign-in to Firezone is left open and ends up getting stuck on the Sign In page with the liveview loader on the top of the page also stuck and appearing as though it is waiting for another response. This commit adds a sign-in success page that is displayed upon successful sign-in and shows a message to the user that lets them know they can close the window if needed. If the client device is able to close the web view that was opened, then the page will either very briefly be shown or will not be visible at all due to how quickly the redirect happens.
This commit is contained in:
@@ -118,18 +118,12 @@ defmodule Web.Auth do
|
||||
fragment: encoded_fragment,
|
||||
state: state,
|
||||
actor_name: identity.actor.name,
|
||||
account_slug: conn.assigns.account.slug,
|
||||
account_name: conn.assigns.account.name,
|
||||
identity_provider_identifier: identity.provider_identifier
|
||||
}
|
||||
|> Enum.reject(&is_nil(elem(&1, 1)))
|
||||
|> URI.encode_query()
|
||||
|
||||
client_handler =
|
||||
Domain.Config.fetch_env!(:web, :client_handler)
|
||||
|
||||
Phoenix.Controller.redirect(conn,
|
||||
external: "#{client_handler}handle_client_sign_in_callback?#{query}"
|
||||
to: ~p"/#{conn.assigns.account.slug}/sign_in/success?#{query}"
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
57
elixir/apps/web/lib/web/live/sign_in/success.ex
Normal file
57
elixir/apps/web/lib/web/live/sign_in/success.ex
Normal file
@@ -0,0 +1,57 @@
|
||||
defmodule Web.SignIn.Success do
|
||||
use Web, {:live_view, layout: {Web.Layouts, :public}}
|
||||
|
||||
def mount(params, _session, socket) do
|
||||
if connected?(socket) do
|
||||
Process.send_after(self(), :redirect_client, 100)
|
||||
end
|
||||
|
||||
query_params =
|
||||
params
|
||||
|> Map.take(~w[fragment state actor_name identity_provider_identifier])
|
||||
|> Map.put("account_slug", socket.assigns.account.slug)
|
||||
|> Map.put("account_name", socket.assigns.account.name)
|
||||
|
||||
socket = assign(socket, :params, query_params)
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<section>
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto lg:py-0">
|
||||
<.logo />
|
||||
|
||||
<div class="w-full col-span-6 mx-auto bg-white rounded shadow md:mt-0 sm:max-w-lg xl:p-0">
|
||||
<div class="p-6 space-y-4 lg:space-y-6 sm:p-8">
|
||||
<h1 class="text-xl text-center leading-tight tracking-tight text-neutral-900 sm:text-2xl">
|
||||
<span>
|
||||
Sign in successful.
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-center">You may close this window.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_info(:redirect_client, socket) do
|
||||
{scheme, url} =
|
||||
Domain.Config.fetch_env!(:web, :client_handler)
|
||||
|> format_redirect_url()
|
||||
|
||||
query = URI.encode_query(socket.assigns.params)
|
||||
|
||||
{:noreply, redirect(socket, external: {scheme, "#{url}?#{query}"})}
|
||||
end
|
||||
|
||||
defp format_redirect_url(raw_client_handler) do
|
||||
uri = URI.parse(raw_client_handler)
|
||||
|
||||
maybe_host = if uri.host == "", do: "", else: "#{uri.host}:#{uri.port}/"
|
||||
|
||||
{uri.scheme, "//#{maybe_host}handle_client_sign_in_callback"}
|
||||
end
|
||||
end
|
||||
@@ -83,6 +83,10 @@ defmodule Web.Router do
|
||||
scope "/:account_id_or_slug", Web do
|
||||
pipe_through [:browser, :account]
|
||||
|
||||
live_session :client_redirect, on_mount: [Web.Sandbox, {Web.Auth, :mount_account}] do
|
||||
live "/sign_in/success", SignIn.Success
|
||||
end
|
||||
|
||||
scope "/sign_in/providers/:provider_id" do
|
||||
# UserPass
|
||||
post "/verify_credentials", AuthController, :verify_credentials
|
||||
|
||||
@@ -31,11 +31,16 @@ defmodule Web.Sandbox do
|
||||
end
|
||||
|
||||
def allow_live_ecto_sandbox(socket) do
|
||||
user_agent = Phoenix.LiveView.get_connect_info(socket, :user_agent)
|
||||
|
||||
if Phoenix.LiveView.connected?(socket) do
|
||||
user_agent = Phoenix.LiveView.get_connect_info(socket, :user_agent)
|
||||
Sandbox.allow(Phoenix.Ecto.SQL.Sandbox, user_agent)
|
||||
end
|
||||
|
||||
with %{owner: test_pid} <- Phoenix.Ecto.SQL.Sandbox.decode_metadata(user_agent) do
|
||||
Process.put(:last_caller_pid, test_pid)
|
||||
end
|
||||
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,6 +112,10 @@ defmodule Web.AcceptanceCase.Auth do
|
||||
Plug.Conn.send_resp(conn, 200, "Client redirected")
|
||||
end)
|
||||
|
||||
Bypass.stub(bypass, "GET", "/favicon.ico", fn conn ->
|
||||
Plug.Conn.send_resp(conn, 404, "")
|
||||
end)
|
||||
|
||||
bypass
|
||||
end
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ defmodule Web.Acceptance.SignIn.EmailTest do
|
||||
|
||||
session
|
||||
|> email_login_flow(account, identity.provider_identifier, redirect_params)
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
@@ -110,6 +112,8 @@ defmodule Web.Acceptance.SignIn.EmailTest do
|
||||
# And then to a client
|
||||
session
|
||||
|> email_login_flow(account, identity.provider_identifier, redirect_params)
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ defmodule Web.Acceptance.Auth.OpenIDConnectTest do
|
||||
provider_identifier: entity_id
|
||||
)
|
||||
|
||||
# Sign In as an portal user
|
||||
# Sign In as a portal user
|
||||
session
|
||||
|> visit(~p"/#{account}")
|
||||
|> assert_el(Query.text("Sign in to #{account.name}"))
|
||||
@@ -187,6 +187,8 @@ defmodule Web.Acceptance.Auth.OpenIDConnectTest do
|
||||
|> visit(~p"/#{account}?#{redirect_params}")
|
||||
|> assert_el(Query.text("Sign in to #{account.name}"))
|
||||
|> click(Query.link("Sign in with Vault"))
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
@@ -240,6 +242,8 @@ defmodule Web.Acceptance.Auth.OpenIDConnectTest do
|
||||
|> assert_el(Query.text("Sign in to #{account.name}"))
|
||||
|> click(Query.link("Sign in with Vault"))
|
||||
|> Vault.userpass_flow(oidc_login, oidc_password)
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
|
||||
@@ -167,6 +167,8 @@ defmodule Web.Acceptance.Auth.UserPassTest do
|
||||
|
||||
session
|
||||
|> password_login_flow(account, identity.provider_identifier, password, redirect_params)
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
@@ -230,6 +232,8 @@ defmodule Web.Acceptance.Auth.UserPassTest do
|
||||
# And then to a client
|
||||
session
|
||||
|> password_login_flow(account, identity.provider_identifier, password, redirect_params)
|
||||
|> assert_el(Query.text("Sign in successful"))
|
||||
|> assert_path(~p"/#{account}/sign_in/success")
|
||||
|> assert_el(Query.text("Client redirected"))
|
||||
|> assert_path(~p"/handle_client_sign_in_callback")
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ defmodule Web.AuthTest do
|
||||
assert conn.assigns.flash["error"] == "Please use a client application to access Firezone."
|
||||
end
|
||||
|
||||
test "redirects regular users to the deep link for client contexts", %{
|
||||
test "redirects regular users to the sign in success page for client contexts", %{
|
||||
conn: conn,
|
||||
context: context,
|
||||
account: account,
|
||||
@@ -270,11 +270,9 @@ defmodule Web.AuthTest do
|
||||
|> signed_in(provider, identity, context, encoded_fragment, redirect_params)
|
||||
|> redirected_to()
|
||||
|
||||
assert redirected_to =~ "firezone-fd0020211111://handle_client_sign_in_callback"
|
||||
assert redirected_to =~ "#{account.slug}/sign_in/success"
|
||||
assert redirected_to =~ "fragment=#{URI.encode_www_form(encoded_fragment)}"
|
||||
assert redirected_to =~ "state=STATE"
|
||||
assert redirected_to =~ "account_slug=#{account.slug}"
|
||||
assert redirected_to =~ "account_name=#{URI.encode_www_form(account.name)}"
|
||||
|
||||
assert redirected_to =~
|
||||
"identity_provider_identifier=#{URI.encode_www_form(identity.provider_identifier)}"
|
||||
@@ -299,11 +297,9 @@ defmodule Web.AuthTest do
|
||||
|> signed_in(provider, identity, context, encoded_fragment, redirect_params)
|
||||
|> redirected_to()
|
||||
|
||||
assert redirected_to =~ "firezone-fd0020211111://handle_client_sign_in_callback"
|
||||
assert redirected_to =~ "#{account.slug}/sign_in/success"
|
||||
assert redirected_to =~ "fragment=#{URI.encode_www_form(encoded_fragment)}"
|
||||
assert redirected_to =~ "state=STATE"
|
||||
assert redirected_to =~ "account_slug=#{account.slug}"
|
||||
assert redirected_to =~ "account_name=#{URI.encode_www_form(account.name)}"
|
||||
|
||||
assert redirected_to =~
|
||||
"identity_provider_identifier=#{URI.encode_www_form(identity.provider_identifier)}"
|
||||
@@ -780,11 +776,9 @@ defmodule Web.AuthTest do
|
||||
assert conn.halted
|
||||
|
||||
assert redirected_to = redirected_to(conn)
|
||||
assert redirected_to =~ "firezone-fd0020211111://handle_client_sign_in_callback"
|
||||
assert redirected_to =~ "#{account.slug}/sign_in/success"
|
||||
assert redirected_to =~ "fragment=#{URI.encode_www_form(encoded_fragment)}"
|
||||
assert redirected_to =~ "state=STATE"
|
||||
assert redirected_to =~ "account_slug=#{account.slug}"
|
||||
assert redirected_to =~ "account_name=#{URI.encode_www_form(account.name)}"
|
||||
|
||||
assert redirected_to =~
|
||||
"identity_provider_identifier=#{URI.encode_www_form(admin_identity.provider_identifier)}"
|
||||
|
||||
@@ -272,8 +272,7 @@ defmodule Web.AuthControllerTest do
|
||||
|
||||
assert redirected_to = redirected_to(conn)
|
||||
assert redirected_to_uri = URI.parse(redirected_to)
|
||||
assert redirected_to_uri.scheme == "firezone-fd0020211111"
|
||||
assert redirected_to_uri.host == "handle_client_sign_in_callback"
|
||||
assert redirected_to_uri.path == "/#{account.slug}/sign_in/success"
|
||||
|
||||
assert %{
|
||||
"identity_provider_identifier" => identity_provider_identifier,
|
||||
@@ -626,8 +625,7 @@ defmodule Web.AuthControllerTest do
|
||||
refute Map.has_key?(conn.cookies, "fz_auth_state_#{provider.id}")
|
||||
|
||||
assert redirected_to = conn |> redirected_to() |> URI.parse()
|
||||
assert redirected_to.scheme == "firezone-fd0020211111"
|
||||
assert redirected_to.host == "handle_client_sign_in_callback"
|
||||
assert redirected_to.path == "/#{account.slug}/sign_in/success"
|
||||
|
||||
assert query_params = URI.decode_query(redirected_to.query)
|
||||
assert not is_nil(query_params["fragment"])
|
||||
@@ -982,8 +980,7 @@ defmodule Web.AuthControllerTest do
|
||||
})
|
||||
|
||||
assert redirected_to = conn |> redirected_to() |> URI.parse()
|
||||
assert redirected_to.scheme == "firezone-fd0020211111"
|
||||
assert redirected_to.host == "handle_client_sign_in_callback"
|
||||
assert redirected_to.path == "/#{account.slug}/sign_in/success"
|
||||
|
||||
assert query_params = URI.decode_query(redirected_to.query)
|
||||
assert not is_nil(query_params["fragment"])
|
||||
|
||||
@@ -104,7 +104,7 @@ defmodule Web.SignIn.EmailTest do
|
||||
})
|
||||
|> submit_form(conn)
|
||||
|
||||
assert redirected_to(conn, 302) =~ "firezone-fd0020211111://handle_client_sign_in_callback"
|
||||
assert redirected_to(conn, 302) =~ "/#{account.slug}/sign_in/success"
|
||||
refute conn.assigns.flash["error"]
|
||||
end
|
||||
|
||||
|
||||
37
elixir/apps/web/test/web/live/sign_in/success_test.exs
Normal file
37
elixir/apps/web/test/web/live/sign_in/success_test.exs
Normal file
@@ -0,0 +1,37 @@
|
||||
defmodule Web.SignIn.SuccessTest do
|
||||
use Web.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
%{account: account}
|
||||
end
|
||||
|
||||
test "redirects to deep link URL", %{
|
||||
account: account,
|
||||
conn: conn
|
||||
} do
|
||||
query_params = %{
|
||||
"actor_name" => "actor_name",
|
||||
"fragment" => "fragment",
|
||||
"identity_provider_identifier" => "identifier",
|
||||
"state" => "state"
|
||||
}
|
||||
|
||||
{:ok, lv, html} =
|
||||
conn
|
||||
|> live(~p"/#{account}/sign_in/success?#{query_params}")
|
||||
|
||||
assert html =~ "success"
|
||||
assert html =~ "close this window"
|
||||
|
||||
expected_query_params =
|
||||
query_params
|
||||
|> Map.put("account_name", account.name)
|
||||
|> Map.put("account_slug", account.slug)
|
||||
|
||||
{path, _flash} = assert_redirect(lv, 500)
|
||||
uri = URI.parse(path)
|
||||
assert URI.decode_query(uri.query) == expected_query_params
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user