From a51bab698fa50a90a2c7c4e02c2c222ee5249c58 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 Aug 2024 09:39:28 -0400 Subject: [PATCH] improvement: pass context down to all actions --- .../components/magic_link.ex | 14 +++-- .../components/password.ex | 7 ++- .../components/password/register_form.ex | 3 + .../components/password/reset_form.ex | 14 +++-- .../components/password/sign_in_form.ex | 12 +++- .../components/sign_in.ex | 7 ++- .../live_session.ex | 8 ++- lib/ash_authentication_phoenix/reset_live.ex | 2 + lib/ash_authentication_phoenix/router.ex | 4 +- .../sign_in_live.ex | 2 + test/sign_in_test.exs | 57 ++++++++++++++++++- test/support/accounts/user.ex | 21 ++++++- test/support/phoenix.ex | 9 +++ 13 files changed, 142 insertions(+), 18 deletions(-) diff --git a/lib/ash_authentication_phoenix/components/magic_link.ex b/lib/ash_authentication_phoenix/components/magic_link.ex index 029fe14..8b1f0c5 100644 --- a/lib/ash_authentication_phoenix/components/magic_link.ex +++ b/lib/ash_authentication_phoenix/components/magic_link.ex @@ -40,6 +40,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do required(:strategy) => AshAuthentication.Strategy.t(), optional(:overrides) => [module], optional(:current_tenant) => String.t(), + optional(:context) => map(), optional(:auth_routes_prefix) => String.t() } @@ -50,7 +51,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do strategy = assigns.strategy subject_name = Info.authentication_subject_name!(strategy.resource) - form = blank_form(strategy) + form = blank_form(strategy, assigns[:context] || %{}) socket = socket @@ -59,6 +60,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:label, fn -> nil end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> %{} end) |> assign_new(:auth_routes_prefix, fn -> nil end) {:ok, socket} @@ -130,7 +132,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do socket = socket - |> assign(:form, blank_form(strategy)) + |> assign(:form, blank_form(strategy, socket.assigns[:context] || %{})) socket = if flash do @@ -153,7 +155,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do Map.get(params, param_key, %{}) end - defp blank_form(strategy) do + defp blank_form(strategy, context) do api = Info.authentication_domain!(strategy.resource) subject_name = Info.authentication_subject_name!(strategy.resource) @@ -163,7 +165,11 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do as: subject_name |> to_string(), id: "#{subject_name}-#{Strategy.name(strategy)}-#{strategy.request_action_name}" |> slugify(), - context: %{strategy: strategy, private: %{ash_authentication?: true}} + context: + Ash.Helpers.deep_merge_maps(context, %{ + strategy: strategy, + private: %{ash_authentication?: true} + }) ) end end diff --git a/lib/ash_authentication_phoenix/components/password.ex b/lib/ash_authentication_phoenix/components/password.ex index 1c0ef77..6bdbe44 100644 --- a/lib/ash_authentication_phoenix/components/password.ex +++ b/lib/ash_authentication_phoenix/components/password.ex @@ -83,7 +83,8 @@ defmodule AshAuthentication.Phoenix.Components.Password do optional(:overrides) => [module], optional(:live_action) => :sign_in | :register, optional(:path) => String.t(), - optional(:current_tenant) => String.t() + optional(:current_tenant) => String.t(), + optional(:context) => map() } slot :sign_in_extra @@ -143,6 +144,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do |> assign_new(:reset_path, fn -> nil end) |> assign_new(:register_path, fn -> nil end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> %{} end) |> assign_new(:auth_routes_prefix, fn -> nil end) show = @@ -167,6 +169,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do label={false} overrides={@overrides} current_tenant={@current_tenant} + context={@context} > <%= if @sign_in_extra do %>
@@ -212,6 +215,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do label={false} overrides={@overrides} current_tenant={@current_tenant} + context={@context} > <%= if @register_extra do %>
@@ -254,6 +258,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do label={false} overrides={@overrides} current_tenant={@current_tenant} + context={@context} > <%= if @reset_extra do %>
diff --git a/lib/ash_authentication_phoenix/components/password/register_form.ex b/lib/ash_authentication_phoenix/components/password/register_form.ex index cc33d74..8547609 100644 --- a/lib/ash_authentication_phoenix/components/password/register_form.ex +++ b/lib/ash_authentication_phoenix/components/password/register_form.ex @@ -45,6 +45,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.RegisterForm do optional(:overrides) => [module], optional(:live_action) => :sign_in | :register, optional(:current_tenant) => String.t(), + optional(:context) => map(), optional(:auth_routes_prefix) => String.t() } @@ -80,6 +81,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.RegisterForm do |> assign_new(:inner_block, fn -> nil end) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> %{} end) |> assign_new(:auth_routes_prefix, fn -> nil end) {:ok, socket} @@ -158,6 +160,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.RegisterForm do before_submit: fn changeset -> changeset |> Ash.Changeset.set_context(%{token_type: :sign_in}) + |> Ash.Changeset.set_context(socket.assigns[:context] || %{}) |> Ash.Changeset.set_tenant(socket.assigns.current_tenant) end ) do diff --git a/lib/ash_authentication_phoenix/components/password/reset_form.ex b/lib/ash_authentication_phoenix/components/password/reset_form.ex index f18585d..401e89a 100644 --- a/lib/ash_authentication_phoenix/components/password/reset_form.ex +++ b/lib/ash_authentication_phoenix/components/password/reset_form.ex @@ -46,6 +46,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do optional(:label) => String.t() | false, optional(:overrides) => [module], optional(:current_tenant) => String.t(), + optional(:context) => map(), optional(:auth_routes_prefix) => String.t() } @@ -54,7 +55,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do @spec update(props, Socket.t()) :: {:ok, Socket.t()} def update(assigns, socket) do strategy = assigns.strategy - form = blank_form(strategy) + form = blank_form(strategy, assigns[:context] || %{}) socket = socket @@ -64,6 +65,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do |> assign_new(:inner_block, fn -> nil end) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> nil end) |> assign_new(:auth_routes_prefix, fn -> nil end) {:ok, socket} @@ -143,7 +145,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do socket = socket - |> assign(:form, blank_form(strategy)) + |> assign(:form, blank_form(strategy, socket.assigns[:context] || %{})) socket = if flash do @@ -166,7 +168,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do Map.get(params, param_key, %{}) end - defp blank_form(%{resettable: resettable} = strategy) when not is_nil(resettable) do + defp blank_form(%{resettable: resettable} = strategy, context) when not is_nil(resettable) do api = Info.authentication_domain!(strategy.resource) subject_name = Info.authentication_subject_name!(strategy.resource) @@ -177,7 +179,11 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do id: "#{subject_name}-#{Strategy.name(strategy)}-#{resettable.request_password_reset_action_name}" |> slugify(), - context: %{strategy: strategy, private: %{ash_authentication?: true}} + context: + Ash.Helpers.deep_merge_maps(context, %{ + strategy: strategy, + private: %{ash_authentication?: true} + }) ) end end diff --git a/lib/ash_authentication_phoenix/components/password/sign_in_form.ex b/lib/ash_authentication_phoenix/components/password/sign_in_form.ex index f8599ca..8a58fb6 100644 --- a/lib/ash_authentication_phoenix/components/password/sign_in_form.ex +++ b/lib/ash_authentication_phoenix/components/password/sign_in_form.ex @@ -47,6 +47,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do optional(:label) => String.t() | false, optional(:overrides) => [module], optional(:current_tenant) => String.t(), + optional(:context) => map(), optional(:auth_routes_prefix) => String.t() } @@ -66,8 +67,12 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do id: "#{subject_name}-#{Strategy.name(strategy)}-#{strategy.sign_in_action_name}" |> slugify(), - tenant: socket.assigns[:current_tenant], - context: %{strategy: strategy, private: %{ash_authentication?: true}} + tenant: assigns[:current_tenant], + context: + Ash.Helpers.deep_merge_maps(assigns[:context] || %{}, %{ + strategy: strategy, + private: %{ash_authentication?: true} + }) ) socket = @@ -78,6 +83,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do |> assign_new(:inner_block, fn -> nil end) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> %{} end) |> assign_new(:auth_routes_prefix, fn -> nil end) {:ok, socket} @@ -96,6 +102,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do <.form :let={form} for={@form} + id={@form.id} phx-change="change" phx-submit="submit" phx-trigger-action={@trigger_action} @@ -115,6 +122,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do "-submit"} form={form} action={:sign_in} disable_text={override_for(@overrides, :disable_button_text)} diff --git a/lib/ash_authentication_phoenix/components/sign_in.ex b/lib/ash_authentication_phoenix/components/sign_in.ex index 43cbaca..ba49d06 100644 --- a/lib/ash_authentication_phoenix/components/sign_in.ex +++ b/lib/ash_authentication_phoenix/components/sign_in.ex @@ -50,7 +50,8 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do optional(:path) => String.t(), optional(:reset_path) => String.t(), optional(:register_path) => String.t(), - optional(:current_tenant) => String.t() + optional(:current_tenant) => String.t(), + optional(:context) => map() } @doc false @@ -81,6 +82,7 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do |> assign_new(:reset_path, fn -> nil end) |> assign_new(:register_path, fn -> nil end) |> assign_new(:current_tenant, fn -> nil end) + |> assign_new(:context, fn -> %{} end) |> assign_new(:auth_routes_prefix, fn -> nil end) {:ok, socket} @@ -109,6 +111,7 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do register_path={@register_path} overrides={@overrides} current_tenant={@current_tenant} + context={@context} /> <% end %> <% end %> @@ -133,6 +136,7 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do register_path={@register_path} overrides={@overrides} current_tenant={@current_tenant} + context={@context} /> <% end %> <% end %> @@ -155,6 +159,7 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do live_action={@live_action} overrides={@overrides} current_tenant={@current_tenant} + context={@context} />
""" diff --git a/lib/ash_authentication_phoenix/live_session.ex b/lib/ash_authentication_phoenix/live_session.ex index 1e8ff20..d82e5d1 100644 --- a/lib/ash_authentication_phoenix/live_session.ex +++ b/lib/ash_authentication_phoenix/live_session.ex @@ -99,6 +99,7 @@ defmodule AshAuthentication.Phoenix.LiveSession do tenant = session["tenant"] socket = assign(socket, current_tenant: tenant) + context = session["context"] || %{} socket = socket @@ -115,7 +116,10 @@ defmodule AshAuthentication.Phoenix.LiveSession do assign_new(socket, current_subject_name, fn -> if value = session[subject_name] do # credo:disable-for-next-line Credo.Check.Refactor.Nesting - case AshAuthentication.subject_to_user(value, resource, tenant: tenant) do + case AshAuthentication.subject_to_user(value, resource, + tenant: tenant, + context: context + ) do {:ok, user} -> user _ -> nil end @@ -155,10 +159,12 @@ defmodule AshAuthentication.Phoenix.LiveSession do session |> Map.put(subject_name, AshAuthentication.user_to_subject(user)) |> Map.put("tenant", Ash.PlugHelpers.get_tenant(conn)) + |> Map.put("context", Ash.PlugHelpers.get_context(conn)) _ -> session |> Map.put("tenant", Ash.PlugHelpers.get_tenant(conn)) + |> Map.put("context", Ash.PlugHelpers.get_context(conn)) end end) end diff --git a/lib/ash_authentication_phoenix/reset_live.ex b/lib/ash_authentication_phoenix/reset_live.ex index fcdb651..681bfa2 100644 --- a/lib/ash_authentication_phoenix/reset_live.ex +++ b/lib/ash_authentication_phoenix/reset_live.ex @@ -32,6 +32,7 @@ defmodule AshAuthentication.Phoenix.ResetLive do |> assign(overrides: overrides) |> assign_new(:otp_app, fn -> nil end) |> assign(:current_tenant, session["tenant"]) + |> assign(:context, session["context"] || %{}) {:ok, socket} end @@ -56,6 +57,7 @@ defmodule AshAuthentication.Phoenix.ResetLive do token={@token} overrides={@overrides} current_tenant={@current_tenant} + context={@context} />
""" diff --git a/lib/ash_authentication_phoenix/router.ex b/lib/ash_authentication_phoenix/router.ex index 6522443..4fffa3d 100644 --- a/lib/ash_authentication_phoenix/router.ex +++ b/lib/ash_authentication_phoenix/router.ex @@ -403,6 +403,8 @@ defmodule AshAuthentication.Phoenix.Router do @doc false def generate_session(conn, session) do - Map.put(session, "tenant", Ash.PlugHelpers.get_tenant(conn)) + session + |> Map.put("tenant", Ash.PlugHelpers.get_tenant(conn)) + |> Map.put("context", Ash.PlugHelpers.get_context(conn)) end end diff --git a/lib/ash_authentication_phoenix/sign_in_live.ex b/lib/ash_authentication_phoenix/sign_in_live.ex index 1a4c865..f79b706 100644 --- a/lib/ash_authentication_phoenix/sign_in_live.ex +++ b/lib/ash_authentication_phoenix/sign_in_live.ex @@ -37,6 +37,7 @@ defmodule AshAuthentication.Phoenix.SignInLive do |> assign(:reset_path, session["reset_path"]) |> assign(:register_path, session["register_path"]) |> assign(:current_tenant, session["tenant"]) + |> assign(:context, session["context"] || %{}) |> assign(:auth_routes_prefix, session["auth_routes_prefix"]) {:ok, socket} @@ -64,6 +65,7 @@ defmodule AshAuthentication.Phoenix.SignInLive do id={override_for(@overrides, :sign_in_id, "sign-in")} overrides={@overrides} current_tenant={@current_tenant} + context={@context} />
""" diff --git a/test/sign_in_test.exs b/test/sign_in_test.exs index 7f7207e..c6b914e 100644 --- a/test/sign_in_test.exs +++ b/test/sign_in_test.exs @@ -1,8 +1,10 @@ defmodule AshAuthentication.Phoenix.SignInTest do @moduledoc false - use ExUnit.Case + use ExUnit.Case, async: false import Phoenix.ConnTest import Phoenix.LiveViewTest + require Ash.Query + @endpoint AshAuthentication.Phoenix.Test.Endpoint setup do @@ -15,4 +17,57 @@ defmodule AshAuthentication.Phoenix.SignInTest do assert {:ok, _view, html} = live(conn) assert html =~ "Sign in" end + + test "sign_in routes allow a user to sign in", %{conn: conn} do + Example.Accounts.User + |> Ash.Changeset.for_create(:register_with_password, %{ + email: "zach@example.com", + password: "so-secure!", + password_confirmation: "so-secure!" + }) + |> Ash.create!() + + conn = get(conn, "/sign-in") + assert {:ok, lv, _html} = live(conn) + + lv + |> form("#user-password-sign-in-with-password", %{ + "user" => %{ + "email" => "zach@example.com", + "password" => "so-secure!" + } + }) + |> render_submit() + + assert_received {_ref, {:redirect, _topic, %{to: new_to}}} + assert %{path: "/auth/user/password/sign_in_with_token"} = URI.parse(new_to) + end + + describe "context" do + setup do + Application.put_env(:ash_authentication_phoenix, :test_context, %{should_fail: true}) + + on_exit(fn -> + Application.put_env(:ash_authentication_phoenix, :test_context, nil) + end) + end + + test "context is preserved across requests", %{conn: conn} do + Process.put(:test_context, %{should_fail: true}) + conn = get(conn, "/sign-in") + assert {:ok, lv, _html} = live(conn) + + result = + lv + |> form("#user-password-sign-in-with-password", %{ + "user" => %{ + "email" => "zach@example.com", + "password" => "so-secure!" + } + }) + |> render_submit() + + assert result =~ "I cant let you do that dave" + end + end end diff --git a/test/support/accounts/user.ex b/test/support/accounts/user.ex index 12e6425..ecf24e0 100644 --- a/test/support/accounts/user.ex +++ b/test/support/accounts/user.ex @@ -51,6 +51,22 @@ defmodule Example.Accounts.User do end end + preparations do + prepare fn query, _ -> + if query.action.name == :sign_in_with_password && query.context[:should_fail] do + Ash.Query.add_error( + query, + Ash.Error.Query.InvalidArgument.exception( + field: :email, + message: "I cant let you do that dave." + ) + ) + else + query + end + end + end + attributes do uuid_primary_key :id @@ -111,10 +127,9 @@ defmodule Example.Accounts.User do tokens do enabled?(true) token_resource(Example.Accounts.Token) + store_all_tokens? true - signing_secret(fn _, _ -> - Application.fetch_env(:ash_authentication_phoenix, :signing_secret) - end) + signing_secret("fake_secret") end end diff --git a/test/support/phoenix.ex b/test/support/phoenix.ex index 5dcdd8c..c6296a7 100644 --- a/test/support/phoenix.ex +++ b/test/support/phoenix.ex @@ -69,6 +69,7 @@ defmodule AshAuthentication.Phoenix.Test.Router do plug :put_secure_browser_headers plug :load_from_session + plug :put_test_context end scope "/", AshAuthentication.Phoenix.Test do @@ -85,6 +86,14 @@ defmodule AshAuthentication.Phoenix.Test.Router do live("/", HomeLive, :index) end + + @doc false + def put_test_context(conn, _) do + case Application.get_env(:ash_authentication_phoenix, :test_context) do + nil -> conn + context -> Ash.PlugHelpers.set_context(conn, context) + end + end end defmodule AshAuthentication.Phoenix.Test.Endpoint do