defmodule AshAuthentication.Info do
@moduledoc """
Generated configuration functions based on a resource's DSL configuration.
"""
use Spark.InfoGenerator,
extension: AshAuthentication,
sections: [:authentication]
alias Ash.{Changeset, Query}
alias AshAuthentication.Strategy
alias Spark.Dsl.Extension
@type dsl_or_resource :: module | map
@doc """
Retrieve a named strategy from a resource.
"""
@spec strategy(dsl_or_resource | module, atom) :: {:ok, strategy} | :error
when strategy: struct
def strategy(dsl_or_resource, name) do
dsl_or_resource
|> authentication_strategies()
|> Stream.concat(authentication_add_ons(dsl_or_resource))
|> Enum.find_value(:error, fn strategy ->
if Strategy.name(strategy) == name, do: {:ok, strategy}
end)
end
@doc """
Retrieve a named strategy from a resource (raising version).
"""
@spec strategy!(dsl_or_resource | module, atom) :: strategy | no_return
when strategy: struct
def strategy!(dsl_or_resource, name) do
case strategy(dsl_or_resource, name) do
{:ok, strategy} ->
strategy
:error ->
raise "No strategy named `#{inspect(name)}` found on resource `#{inspect(dsl_or_resource)}`"
end
end
@doc """
Given an action name, retrieve the strategy it is for from the DSL
configuration.
"""
@spec strategy_for_action(dsl_or_resource, atom) :: {:ok, Strategy.t()} | :error
def strategy_for_action(dsl_or_resource, action_name) do
case Extension.get_persisted(dsl_or_resource, {:authentication_action, action_name}) do
nil -> :error
value -> {:ok, value}
end
end
@doc """
Given an action name, retrieve the strategy it is for from the DSL
configuration.
"""
@spec strategy_for_action!(dsl_or_resource, atom) :: Strategy.t() | no_return
def strategy_for_action!(dsl_or_resource, action_name) do
case strategy_for_action(dsl_or_resource, action_name) do
{:ok, value} ->
value
:error ->
raise "No strategy action named `#{inspect(action_name)}` found on resource `#{inspect(dsl_or_resource)}`"
end
end
@doc """
Find the underlying strategy that required a change/preparation to be used.
This is because the `strategy_name` can be passed on the change options, eg:
```elixir
change {AshAuthentication.Strategy.Password.HashPasswordChange, strategy_name: :banana_custard}
```
Or via the action context, eg:
```elixir
prepare set_context(%{strategy_name: :banana_custard})
prepare AshAuthentication.Strategy.Password.SignInPreparation
```
Or via the passed-in context on calling the action.
"""
@spec find_strategy(Query.t() | Changeset.t(), context, options) :: {:ok, Strategy.t()} | :error
when context: map, options: Keyword.t()
def find_strategy(queryset, context \\ %{}, options) do
with :error <- Keyword.fetch(options, :strategy_name),
:error <- Map.fetch(context, :strategy_name),
:error <- Map.fetch(queryset.context, :strategy_name),
:error <- strategy_for_action(queryset.resource, queryset.action.name) do
:error
else
{:ok, strategy_name} when is_atom(strategy_name) ->
strategy(queryset.resource, strategy_name)
{:ok, strategy} ->
{:ok, strategy}
end
end
end