Packages
ash_authentication
4.3.8
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/password/actions.ex
defmodule AshAuthentication.Strategy.Password.Actions do
@moduledoc """
Actions for the password strategy
Provides the code interface for working with resources via a password
strategy.
"""
alias Ash.{Changeset, Error.Invalid.NoSuchAction, Query, Resource}
alias AshAuthentication.{Errors, Info, Jwt, Strategy.Password, TokenResource}
@doc """
Attempt to sign in a user.
"""
@spec sign_in(Password.t(), map, keyword) ::
{:ok, Resource.record()} | {:error, Errors.AuthenticationFailed.t()}
def sign_in(strategy, params, options)
when is_struct(strategy, Password) and strategy.sign_in_enabled? do
{context, options} = Keyword.pop(options, :context, [])
context =
context
|> Map.new()
|> Map.merge(%{
private: %{
ash_authentication?: true
}
})
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
strategy.resource
|> Query.new()
|> Query.ensure_selected(List.wrap(strategy.require_confirmed_with))
|> Query.set_context(context)
|> Query.for_read(strategy.sign_in_action_name, params, options)
|> Ash.read()
|> case do
{:ok, [user]} ->
case strategy.require_confirmed_with do
nil ->
{:ok, user}
field ->
if user_confirmed?(user, field) do
{:ok, user}
else
{:error,
Errors.UnconfirmedUser.exception(
strategy: strategy,
caused_by: %{
action: :sign_in,
message: "User must be confirmed to sign in.",
module: __MODULE__
}
)}
end
end
{:ok, []} ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Query returned no users"
}
)}
{:ok, _users} ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Query returned too many users"
}
)}
{:error, error} when is_exception(error) ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: error
)}
{:error, error} ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Query returned error: #{inspect(error)}"
}
)}
end
end
def sign_in(strategy, _params, _options) when is_struct(strategy, Password) do
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Attempt to sign in with sign in disabled."
}
)}
end
@doc """
Attempt to sign in a previously-authenticated user with a short-lived sign in token.
"""
@spec sign_in_with_token(Password.t(), map, keyword) :: {:ok, Resource.record()} | {:error, any}
def sign_in_with_token(strategy, params, options) when is_struct(strategy, Password) do
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
strategy.resource
|> Query.new()
|> Query.set_context(%{private: %{ash_authentication?: true}})
|> Query.for_read(strategy.sign_in_with_token_action_name, %{"token" => params["token"]})
|> Ash.read(options)
|> case do
{:ok, [user]} ->
{:ok, user}
{:error, error} when is_struct(error, Errors.AuthenticationFailed) ->
{:error, error}
{:error, error} when is_exception(error) ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: strategy.sign_in_with_token_action_name,
message: Exception.message(error)
}
)}
{:error, reason} ->
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: strategy.sign_in_with_token_action_name,
message: reason
}
)}
end
end
@doc """
Attempt to register a new user.
"""
@spec register(Password.t(), map, keyword) :: {:ok, Resource.record()} | {:error, any}
def register(strategy, params, options)
when is_struct(strategy, Password) and strategy.registration_enabled? == true do
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
strategy.resource
|> Changeset.new()
|> Changeset.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Changeset.for_create(strategy.register_action_name, params, options)
|> Ash.create()
end
def register(strategy, _params, _options) when is_struct(strategy, Password) do
{:error,
Errors.AuthenticationFailed.exception(
strategy: strategy,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :register,
message: "Attempt to register a new user with registration disabled."
}
)}
end
@doc """
Request a password reset.
"""
@spec reset_request(Password.t(), map, keyword) :: :ok | {:error, any}
def reset_request(
%Password{resettable: %Password.Resettable{} = resettable} = strategy,
params,
options
) do
case Ash.Resource.Info.action(
strategy.resource,
resettable.request_password_reset_action_name
) do
nil ->
{:error,
NoSuchAction.exception(resource: strategy.resource, action: :reset_request, type: :read)}
%{type: :read, name: action_name} ->
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
strategy.resource
|> Query.new()
|> Query.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Query.for_read(action_name, params, options)
|> Ash.read()
|> case do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
%{type: :action, name: action_name} ->
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
strategy.resource
|> Ash.ActionInput.new()
|> Ash.ActionInput.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Ash.ActionInput.for_action(action_name, params, options)
|> Ash.run_action()
|> case do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end
end
def reset_request(%Password{} = strategy, _params, _options),
do:
{:error,
NoSuchAction.exception(resource: strategy.resource, action: :reset_request, type: :read)}
@doc """
Attempt to change a user's password using a reset token.
"""
@spec reset(Password.t(), map, keyword) :: {:ok, Resource.record()} | {:error, any}
def reset(
%Password{resettable: %Password.Resettable{} = resettable} = strategy,
params,
options
) do
with {:ok, token} <- Map.fetch(params, "reset_token"),
{:ok, %{"sub" => subject}, resource} <- Jwt.verify(token, strategy.resource),
{:ok, user} <- AshAuthentication.subject_to_user(subject, resource, options) do
options =
options
|> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
user
|> Changeset.new()
|> Changeset.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Changeset.for_update(resettable.password_reset_action_name, params, options)
|> Changeset.after_action(fn _changeset, record ->
token_resource = Info.authentication_tokens_token_resource!(resource)
:ok = TokenResource.revoke(token_resource, token, options)
{:ok, record}
end)
|> Ash.update()
else
{:error, %Changeset{} = changeset} -> {:error, changeset}
_ -> {:error, Errors.InvalidToken.exception(type: :reset)}
end
end
def reset(strategy, _params, _options) when is_struct(strategy, Password),
do: {:error, NoSuchAction.exception(resource: strategy.resource, action: :reset, type: :read)}
defp user_confirmed?(user, field) do
case Map.get(user, field) do
%Ash.NotLoaded{} -> false
%Ash.ForbiddenField{} -> false
nil -> false
_ -> true
end
end
end