Current section

Files

Jump to
ash_authentication lib ash_authentication add_ons confirmation confirm_change.ex
Raw

lib/ash_authentication/add_ons/confirmation/confirm_change.ex

# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT
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, context)
:error ->
raise AssumptionFailed,
message: "Action does not correlate with an authentication strategy"
end
end
defp do_change(changeset, strategy, context) do
changeset
|> Changeset.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Changeset.before_action(&apply_confirmation_token(&1, strategy, context))
end
defp apply_confirmation_token(changeset, strategy, context) do
with token when is_binary(token) <-
Changeset.get_argument(changeset, :confirm),
{:ok, %{"act" => action, "jti" => jti}, _} <-
Jwt.verify(token, changeset.resource, Ash.Context.to_opts(context)),
true <-
to_string(strategy.confirm_action_name) == action,
{:ok, changes} <- Actions.get_changes(strategy, jti, Ash.Context.to_opts(context)) do
allowed_changes =
if strategy.inhibit_updates?,
do: Map.take(changes, Enum.map(strategy.monitor_fields, &to_string/1)),
else: %{}
changeset
|> Changeset.force_change_attributes(allowed_changes)
|> Changeset.force_change_attribute(strategy.confirmed_at_field, DateTime.utc_now())
else
_ ->
Changeset.add_error(
changeset,
InvalidArgument.exception(field: :confirm, message: "is not valid")
)
end
end
end