Packages
ash_authentication
5.0.0-rc.10
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.
Current section
Files
Jump to
Current section
Files
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, UserIdentity}
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())
|> maybe_link_identity(strategy, jti, context)
else
_ ->
Changeset.add_error(
changeset,
InvalidArgument.exception(field: :confirm, message: "is not valid")
)
end
end
# `on_untrusted_email_match :confirm`: when the confirmed token carries a
# pending provider identity link, create it once the user is confirmed. The
# token itself is revoked by `Confirmation.Actions.confirm/3`, so the link
# cannot be replayed.
defp maybe_link_identity(changeset, strategy, jti, context) do
case Actions.get_identity_link(strategy, jti, Ash.Context.to_opts(context)) do
{:ok, payload} ->
Changeset.after_action(changeset, fn _changeset, user ->
link_identity(user, payload, context)
end)
:error ->
changeset
end
end
defp link_identity(user, payload, context) do
with {:ok, oauth_strategy} <-
Info.strategy(user.__struct__, String.to_existing_atom(payload["strategy"])),
{:ok, _identity} <-
UserIdentity.Actions.upsert(
oauth_strategy.identity_resource,
%{
user_info: payload["user_info"],
oauth_tokens: payload["oauth_tokens"],
strategy: oauth_strategy.name,
user_id: user.id
},
Ash.Context.to_opts(context)
) do
{:ok, user}
end
end
end