defmodule AshAuthentication.AddOn.Confirmation.ConfirmChange do
@moduledoc """
Performs a change based on the contents of a confirmation token.
"""
use Ash.Resource.Change
alias AshAuthentication.{AddOn.Confirmation.Actions, Info, Jwt}
alias Ash.{
Changeset,
Error.Changes.InvalidArgument,
Error.Framework.AssumptionFailed,
Resource.Change
}
@doc false
@impl true
@spec change(Changeset.t(), keyword, Change.context()) :: Changeset.t()
def change(changeset, _opts, _context) do
case Info.strategy_for_action(changeset.resource, changeset.action.name) do
{:ok, strategy} ->
do_change(changeset, strategy)
:error ->
raise AssumptionFailed,
message: "Action does not correlate with an authentication strategy"
end
end
defp do_change(changeset, strategy) do
changeset
|> Changeset.before_action(fn changeset ->
with token when is_binary(token) <- Changeset.get_argument(changeset, :confirm),
{:ok, %{"act" => action, "jti" => jti}, _} <-
Jwt.verify(token, changeset.resource),
true <- to_string(strategy.confirm_action_name) == action,
{:ok, changes} <- Actions.get_changes(strategy, jti) do
allowed_changes =
if strategy.inhibit_updates?,
do: Map.take(changes, Enum.map(strategy.monitor_fields, &to_string/1)),
else: %{}
changeset
|> Changeset.change_attributes(allowed_changes)
|> Changeset.change_attribute(strategy.confirmed_at_field, DateTime.utc_now())
else
_ ->
raise InvalidArgument, field: :confirm, message: "is not valid"
end
end)
end
end