Packages
ash_authentication
4.13.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/strategies/magic_link/sign_in_change.ex
# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT
defmodule AshAuthentication.Strategy.MagicLink.SignInChange do
@moduledoc """
Set up a create action for magic link sign in.
"""
use Ash.Resource.Change
alias Ash.{Changeset, Resource, Resource.Change}
alias AshAuthentication.{Errors.InvalidToken, Info, Jwt, TokenResource}
@doc false
@impl true
@spec change(Changeset.t(), keyword, Change.Context.t()) :: Changeset.t()
def change(changeset, opts, context) do
subject_name =
changeset.resource
|> Info.authentication_subject_name!()
|> to_string()
case Info.find_strategy(changeset, context, opts) do
{:ok, strategy} ->
with {:got_token, token} when is_binary(token) <-
{:got_token, Changeset.get_argument(changeset, strategy.token_param_name)},
{:verified,
{:ok, %{"act" => token_action, "sub" => subject, "identity" => identity}, _}} <-
{:verified,
Jwt.verify(token, changeset.resource, Ash.Context.to_opts(context), context)},
{:action, ^token_action} <-
{:action, to_string(strategy.sign_in_action_name)},
{:subject_matches, %URI{path: ^subject_name}} <-
{:subject_matches, URI.parse(subject)} do
changeset
|> Changeset.force_change_attribute(strategy.identity_field, identity)
|> Changeset.after_transaction(fn
_changeset, {:ok, record} ->
revoke_single_use_token!(strategy, changeset, token, context)
{:ok, token, _claims} =
Jwt.token_for_user(record, %{}, Ash.Context.to_opts(context))
{:ok, Resource.put_metadata(record, :token, token)}
_changeset, {:error, error} ->
{:error, error}
end)
else
e ->
reason = error_reason(e, strategy)
case Info.find_strategy(changeset, context, opts) do
{:ok, strategy} ->
Ash.Changeset.add_error(
changeset,
InvalidToken.exception(
field: strategy.token_param_name,
reason: reason,
type: :magic_link
)
)
end
end
_ ->
Ash.Changeset.add_error(
changeset,
"No strategy in context, and no strategy found for action #{inspect(changeset.resource)}.#{changeset.action.name}"
)
end
end
defp revoke_single_use_token!(strategy, changeset, token, context) do
if strategy.single_use_token? do
token_resource = Info.authentication_tokens_token_resource!(changeset.resource)
:ok = TokenResource.revoke(token_resource, token, Ash.Context.to_opts(context))
end
end
defp error_reason(e, strategy) do
case e do
{:got_token, nil} ->
"No token supplied in #{strategy.token_param_name}"
{:got_token, token} ->
"Expected #{strategy.token_param_name} to be a string, got: #{inspect(token)}"
{:verified, _} ->
"Token in #{strategy.token_param_name} param did not pass verification"
{:action, token_action} ->
"Token in #{strategy.token_param_name} param was for action #{token_action}, expected it to be for #{strategy.sign_in_action_name}"
{:subject_matches, %URI{path: subject_name}} ->
"Expected subject of token to be #{inspect(subject_name)}, got #{subject_name}"
end
end
end