Packages
ash_authentication
3.11.7
5.0.0-rc.12
5.0.0-rc.11
5.0.0-rc.10
5.0.0-rc.9
5.0.0-rc.8
5.0.0-rc.7
5.0.0-rc.6
5.0.0-rc.5
5.0.0-rc.4
5.0.0-rc.3
5.0.0-rc.2
5.0.0-rc.1
5.0.0-rc.0
4.14.1
4.14.0
4.13.7
4.13.6
4.13.5
4.13.4
4.13.3
4.13.2
retired
4.13.1
retired
4.13.0
4.12.0
4.11.0
4.10.0
4.9.9
4.9.8
4.9.7
4.9.6
4.9.5
4.9.4
4.9.3
4.9.2
4.9.1
4.9.0
4.8.7
4.8.6
4.8.5
4.8.3
4.8.2
4.8.1
4.8.0
4.7.6
4.7.5
4.7.4
4.7.3
4.7.2
4.7.1
4.7.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.12
4.3.11
4.3.10
4.3.9
4.3.8
4.3.7
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
4.0.0-rc.6
4.0.0-rc.5
4.0.0-rc.3
4.0.0-rc.2
4.0.0-rc.1
4.0.0-rc.0
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.0
3.7.9
3.7.8
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.1
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.3.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.0
3.0.3
Authentication extension for the Ash Framework.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash_authentication/add_ons/confirmation/transformer.ex
defmodule AshAuthentication.AddOn.Confirmation.Transformer do
@moduledoc """
DSL transformer for confirmation add-on.
Ensures that there is only ever one present and that it is correctly
configured.
"""
alias Ash.{Resource, Type}
alias AshAuthentication.{AddOn.Confirmation, GenerateTokenChange}
alias Spark.{Dsl.Transformer, Error.DslError}
import AshAuthentication.Utils
import AshAuthentication.Validations
import AshAuthentication.Validations.Action
import AshAuthentication.Validations.Attribute
import AshAuthentication.Strategy.Custom.Helpers
@doc false
@spec transform(Confirmation.t(), map) ::
{:ok, Confirmation.t() | map} | {:error, Exception.t()}
def transform(strategy, dsl_state) do
with :ok <-
validate_token_generation_enabled(
dsl_state,
"Token generation must be enabled for password resets to work."
),
:ok <- validate_monitor_fields(dsl_state, strategy),
{:ok, dsl_state} <-
maybe_build_action(
dsl_state,
strategy.confirm_action_name,
&build_confirm_action(&1, strategy)
),
:ok <- validate_confirm_action(dsl_state, strategy),
{:ok, dsl_state} <-
maybe_build_attribute(
dsl_state,
strategy.confirmed_at_field,
&build_confirmed_at_attribute(&1, strategy)
),
:ok <- validate_confirmed_at_attribute(dsl_state, strategy),
{:ok, dsl_state} <-
maybe_build_change(
dsl_state,
{Confirmation.ConfirmationHookChange, strategy_name: strategy.name}
),
{:ok, resource} <- persisted_option(dsl_state, :module) do
strategy = %{strategy | resource: resource}
dsl_state =
dsl_state
|> then(®ister_strategy_actions([strategy.confirm_action_name], &1, strategy))
|> put_add_on(strategy)
{:ok, dsl_state}
else
{:error, reason} when is_binary(reason) ->
{:error,
DslError.exception(path: [:authentication, :add_ons, :confirmation], message: reason)}
{:error, reason} ->
{:error, reason}
:error ->
{:error,
DslError.exception(
path: [:authentication, :add_ons, :confirmation],
message: "Configuration error"
)}
end
end
defp validate_monitor_fields(_dsl_state, %{monitor_fields: []}),
do:
{:error,
DslError.exception(
path: [:authentication, :add_ons, :confirmation],
message: "You should be monitoring at least one field"
)}
defp validate_monitor_fields(dsl_state, strategy) do
Enum.reduce_while(strategy.monitor_fields, :ok, fn field, :ok ->
with {:ok, resource} <- persisted_option(dsl_state, :module),
{:ok, attribute} <- find_attribute(dsl_state, field),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- maybe_validate_eager_checking(dsl_state, strategy, field, resource) do
{:cont, :ok}
else
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp maybe_validate_eager_checking(_dsl_state, %{inhibit_updates?: false}, _, _), do: :ok
defp maybe_validate_eager_checking(dsl_state, _strategy, field, resource) do
dsl_state
|> Resource.Info.identities()
|> Enum.find(&(&1.keys == [field]))
|> case do
%{name: name, eager_check_with: nil} ->
{:error,
DslError.exception(
path: [:identities, :identity],
message:
"The #{name} identity on the resource `#{inspect(resource)}` needs the `eager_check_with` property set so that inhibited changes are still validated."
)}
_ ->
:ok
end
end
defp build_confirm_action(_dsl_state, strategy) do
arguments = [
Transformer.build_entity!(Resource.Dsl, [:actions, :update], :argument,
name: :confirm,
type: Type.String,
allow_nil?: false
)
]
changes = [
Transformer.build_entity!(Resource.Dsl, [:actions, :update], :change,
change: Confirmation.ConfirmChange
),
Transformer.build_entity!(Resource.Dsl, [:actions, :update], :change,
change: GenerateTokenChange
)
]
metadata = [
Transformer.build_entity!(Resource.Dsl, [:actions, :update], :metadata,
name: :token,
type: :string,
allow_nil?: false
)
]
Transformer.build_entity(Resource.Dsl, [:actions], :update,
name: strategy.confirm_action_name,
accept: strategy.monitor_fields,
arguments: arguments,
metadata: metadata,
changes: changes
)
end
defp validate_confirm_action(dsl_state, strategy) do
with {:ok, action} <- validate_action_exists(dsl_state, strategy.confirm_action_name),
:ok <- validate_action_has_change(action, Confirmation.ConfirmChange),
:ok <- validate_action_argument_option(action, :confirm, :allow_nil?, [false]),
:ok <- validate_action_has_change(action, GenerateTokenChange) do
validate_action_argument_option(action, :confirm, :type, [Type.String])
end
end
defp build_confirmed_at_attribute(_dsl_state, strategy) do
Transformer.build_entity(Resource.Dsl, [:attributes], :attribute,
name: strategy.confirmed_at_field,
type: Type.UtcDatetimeUsec,
allow_nil?: true,
writable?: true
)
end
defp validate_confirmed_at_attribute(dsl_state, strategy) do
with {:ok, resource} <- persisted_option(dsl_state, :module),
{:ok, attribute} <- find_attribute(dsl_state, strategy.confirmed_at_field),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [true]),
:ok <- validate_attribute_option(attribute, resource, :type, [Type.UtcDatetimeUsec]) do
:ok
else
:error ->
{:error,
DslError.exception(
path: [:confirmation],
message: "The `confirmed_at_field` option must be set."
)}
{:error, reason} ->
{:error, reason}
end
end
defp maybe_build_change(dsl_state, change_module) do
with changes <- Resource.Info.changes(dsl_state),
false <- Enum.any?(changes, &(&1.change == change_module)),
{:ok, change} <-
Transformer.build_entity(Resource.Dsl, [:changes], :change,
change: change_module,
on: [:create, :update]
) do
{:ok, Transformer.add_entity(dsl_state, [:changes], change)}
else
true -> {:ok, dsl_state}
{:error, reason} -> {:error, reason}
end
end
end