mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-19 12:52:55 +12:00
improvement: add select_for_senders (#189)
* improvement: add select_for_senders fix: select `hashed_password` on sign in preparation * improvement: include metadata declaration on register action * chore: fix typo
This commit is contained in:
parent
ca3dac3878
commit
a2bba519c0
9 changed files with 106 additions and 1 deletions
|
@ -124,6 +124,7 @@ defmodule AshAuthentication do
|
|||
),
|
||||
transformers: [
|
||||
AshAuthentication.Transformer,
|
||||
AshAuthentication.Transformer.SetSelectForSenders,
|
||||
AshAuthentication.Strategy.Custom.Transformer
|
||||
],
|
||||
verifiers: [
|
||||
|
|
|
@ -80,6 +80,16 @@ defmodule AshAuthentication.Dsl do
|
|||
action doesn't exist, one will be generated for you.
|
||||
""",
|
||||
default: :get_by_subject
|
||||
],
|
||||
select_for_senders: [
|
||||
type: {:list, :atom},
|
||||
doc: """
|
||||
A list of fields that we will ensure are selected whenever a sender will be invoked.
|
||||
This is useful if using something like `ash_graphql` which by default only selects
|
||||
what fields appear in the query, and if you are exposing these actions that way.
|
||||
Defaults to `[:email]` if there is an `:email` attribute on the resource, and `[]`
|
||||
otherwise.
|
||||
"""
|
||||
]
|
||||
],
|
||||
sections: [
|
||||
|
|
|
@ -24,9 +24,13 @@ defmodule AshAuthentication.Strategy.MagicLink.RequestPreparation do
|
|||
|
||||
identity_field = strategy.identity_field
|
||||
identity = Query.get_argument(query, identity_field)
|
||||
select_for_senders = Info.authentication_select_for_senders!(query.resource)
|
||||
|
||||
query
|
||||
|> Query.filter(ref(^identity_field) == ^identity)
|
||||
|> Query.before_action(fn query ->
|
||||
Ash.Query.ensure_selected(query, select_for_senders)
|
||||
end)
|
||||
|> Query.after_action(&after_action(&1, &2, strategy))
|
||||
end
|
||||
|
||||
|
|
|
@ -25,9 +25,13 @@ defmodule AshAuthentication.Strategy.Password.RequestPasswordResetPreparation do
|
|||
if Enum.any?(strategy.resettable) do
|
||||
identity_field = strategy.identity_field
|
||||
identity = Query.get_argument(query, identity_field)
|
||||
select_for_senders = Info.authentication_select_for_senders!(query.resource)
|
||||
|
||||
query
|
||||
|> Query.filter(ref(^identity_field) == ^identity)
|
||||
|> Query.before_action(fn query ->
|
||||
Ash.Query.ensure_selected(query, select_for_senders)
|
||||
end)
|
||||
|> Query.after_action(&after_action(&1, &2, strategy))
|
||||
else
|
||||
query
|
||||
|
|
|
@ -27,6 +27,9 @@ defmodule AshAuthentication.Strategy.Password.SignInPreparation do
|
|||
|
||||
query
|
||||
|> Query.filter(ref(^identity_field) == ^identity)
|
||||
|> Query.before_action(fn query ->
|
||||
Ash.Query.ensure_selected(query, [strategy.hashed_password_field])
|
||||
end)
|
||||
|> Query.after_action(fn
|
||||
query, [record] when is_binary(:erlang.map_get(strategy.hashed_password_field, record)) ->
|
||||
password = Query.get_argument(query, strategy.password_field)
|
||||
|
|
|
@ -85,7 +85,7 @@ defmodule AshAuthentication.Strategy.Password.Transformer do
|
|||
end
|
||||
end
|
||||
|
||||
defp build_register_action(_dsl_state, strategy) do
|
||||
defp build_register_action(dsl_state, strategy) do
|
||||
password_opts = [
|
||||
type: Type.String,
|
||||
allow_nil?: false,
|
||||
|
@ -129,10 +129,24 @@ defmodule AshAuthentication.Strategy.Password.Transformer do
|
|||
)
|
||||
])
|
||||
|
||||
metadata =
|
||||
if AshAuthentication.Info.authentication_tokens_enabled?(dsl_state) do
|
||||
[
|
||||
Transformer.build_entity!(Resource.Dsl, [:actions, :create], :metadata,
|
||||
name: :token,
|
||||
type: :string,
|
||||
allow_nil?: false
|
||||
)
|
||||
]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
Transformer.build_entity(Resource.Dsl, [:actions], :create,
|
||||
name: strategy.register_action_name,
|
||||
arguments: arguments,
|
||||
changes: changes,
|
||||
metadata: metadata,
|
||||
allow_nil_input: [strategy.hashed_password_field]
|
||||
)
|
||||
end
|
||||
|
|
34
lib/ash_authentication/transformer/set_select_for_senders.ex
Normal file
34
lib/ash_authentication/transformer/set_select_for_senders.ex
Normal file
|
@ -0,0 +1,34 @@
|
|||
defmodule AshAuthentication.Transformer.SetSelectForSenders do
|
||||
@moduledoc """
|
||||
Sets the `select_for_senders` options to its default value.
|
||||
"""
|
||||
|
||||
use Spark.Dsl.Transformer
|
||||
alias AshAuthentication.Info
|
||||
alias Spark.Dsl.Transformer
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec after?(any) :: boolean()
|
||||
def after?(_), do: true
|
||||
|
||||
@impl true
|
||||
def transform(dsl_state) do
|
||||
dsl_state
|
||||
|> Info.authentication_select_for_senders()
|
||||
|> case do
|
||||
:error ->
|
||||
if Ash.Resource.Info.attribute(dsl_state, :email) do
|
||||
{:ok,
|
||||
Transformer.set_option(dsl_state, [:authentication], :select_for_senders, [
|
||||
:email
|
||||
])}
|
||||
else
|
||||
{:ok, Transformer.set_option(dsl_state, [:authentication], :select_for_senders, [])}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:ok, dsl_state}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -129,6 +129,39 @@ defmodule AshAuthentication.Strategy.Password.ActionsTest do
|
|||
assert log =~ ~r/password reset request for user #{user.username}/i
|
||||
end
|
||||
|
||||
test "it selects required fields for senders using `select_for_senders`" do
|
||||
user = build_user()
|
||||
{:ok, strategy} = Info.strategy(Example.User, :password)
|
||||
|
||||
log =
|
||||
capture_log(fn ->
|
||||
params = %{"username" => user.username}
|
||||
options = []
|
||||
api = Info.authentication_api!(strategy.resource)
|
||||
resettable = strategy.resettable |> Enum.at(0)
|
||||
|
||||
result =
|
||||
strategy.resource
|
||||
|> Ash.Query.new()
|
||||
|> Ash.Query.set_context(%{
|
||||
private: %{
|
||||
ash_authentication?: true
|
||||
}
|
||||
})
|
||||
|> Ash.Query.for_read(resettable.request_password_reset_action_name, params)
|
||||
|> Ash.Query.select([])
|
||||
|> api.read(options)
|
||||
|> case do
|
||||
{:ok, _} -> :ok
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
assert result == :ok
|
||||
end)
|
||||
|
||||
assert log =~ ~r/password reset request for user #{user.username}/i
|
||||
end
|
||||
|
||||
test "it doesn't generate a reset token when no matching user exists and the strategy is resettable" do
|
||||
{:ok, strategy} = Info.strategy(Example.User, :password)
|
||||
|
||||
|
|
|
@ -130,6 +130,8 @@ defmodule Example.User do
|
|||
authentication do
|
||||
api Example
|
||||
|
||||
select_for_senders([:username])
|
||||
|
||||
tokens do
|
||||
enabled? true
|
||||
store_all_tokens? true
|
||||
|
|
Loading…
Reference in a new issue