improvement: pass context down to all actions

This commit is contained in:
Zach Daniel 2024-08-09 09:39:28 -04:00
parent 9f5feedc7d
commit a51bab698f
13 changed files with 142 additions and 18 deletions

View file

@ -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

View file

@ -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 %>
<div class={override_for(@overrides, :slot_class)}>
@ -212,6 +215,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
label={false}
overrides={@overrides}
current_tenant={@current_tenant}
context={@context}
>
<%= if @register_extra do %>
<div class={override_for(@overrides, :slot_class)}>
@ -254,6 +258,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
label={false}
overrides={@overrides}
current_tenant={@current_tenant}
context={@context}
>
<%= if @reset_extra do %>
<div class={override_for(@overrides, :slot_class)}>

View file

@ -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

View file

@ -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

View file

@ -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
<Password.Input.submit
strategy={@strategy}
id={@form.id <> "-submit"}
form={form}
action={:sign_in}
disable_text={override_for(@overrides, :disable_button_text)}

View file

@ -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}
/>
</div>
"""

View file

@ -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

View file

@ -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}
/>
</div>
"""

View file

@ -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

View file

@ -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}
/>
</div>
"""

View file

@ -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

View file

@ -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

View file

@ -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