Packages
ash_authentication
4.9.3
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/verifier.ex
defmodule AshAuthentication.Strategy.MagicLink.Verifier do
@moduledoc """
DSL verifier for magic links.
"""
alias AshAuthentication.{Strategy.MagicLink}
alias Spark.Error.DslError
import AshAuthentication.Validations
import AshAuthentication.Validations.Action
import AshAuthentication.Validations.Attribute
@doc false
@spec verify(MagicLink.t(), map) :: :ok | {:error, Exception.t()}
def verify(strategy, dsl_state) do
with {:ok, identity_attribute} <- validate_identity_attribute(dsl_state, strategy),
:ok <- validate_request_action(dsl_state, strategy, identity_attribute),
:ok <- prevent_hijacking(dsl_state, strategy) do
validate_sign_in_action(dsl_state, strategy)
end
end
defp validate_identity_attribute(dsl_state, strategy) do
with {:ok, identity_attribute} <- find_attribute(dsl_state, strategy.identity_field),
:ok <-
validate_attribute_unique_constraint(
dsl_state,
[strategy.identity_field],
strategy.resource
) do
{:ok, identity_attribute}
end
end
defp validate_request_action(dsl_state, strategy, identity_attribute) do
with {:ok, action} <- validate_action_exists(dsl_state, strategy.request_action_name),
:ok <- validate_action_has_argument(action, strategy.identity_field),
:ok <-
validate_action_argument_option(
action,
strategy.identity_field,
:type,
[identity_attribute.type]
),
:ok <-
validate_action_argument_option(action, strategy.identity_field, :allow_nil?, [
false
]),
:ok <- validate_field_in_values(action, :type, [:read, :action]) do
case action.type do
:read ->
validate_action_has_preparation(action, MagicLink.RequestPreparation)
_ ->
with {:ok, action} <- validate_action_exists(dsl_state, strategy.lookup_action_name),
:ok <- validate_action_has_argument(action, identity_attribute.name),
:ok <-
validate_action_argument_option(
action,
strategy.identity_field,
:type,
[identity_attribute.type]
),
:ok <-
validate_action_argument_option(action, strategy.identity_field, :allow_nil?, [
false
]),
:ok <- validate_action_option(action, :get?, [true]) do
:ok
else
{:error, error} ->
{:error, error}
end
end
else
{:error, message} when is_binary(message) ->
{:error,
DslError.exception(
path: [:actions, strategy.request_action_name],
message: message
)}
{:error, exception} when is_exception(exception) ->
{:error, exception}
end
end
defp validate_sign_in_action(dsl_state, strategy) do
with {:ok, action} <- validate_action_exists(dsl_state, strategy.sign_in_action_name),
:ok <- validate_action_has_argument(action, strategy.token_param_name),
:ok <-
validate_action_argument_option(action, strategy.token_param_name, :type, [
:string,
Ash.Type.String
]),
:ok <-
validate_action_argument_option(action, strategy.token_param_name, :allow_nil?, [false]),
:ok <- validate_field_in_values(action, :type, [:read, :create]) do
if action.type == :read do
validate_action_has_preparation(action, MagicLink.SignInPreparation)
else
validate_action_has_change(action, MagicLink.SignInChange)
end
else
{:error, message} when is_binary(message) ->
{:error,
DslError.exception(
path: [:actions, :read, strategy.sign_in_action_name, :type],
message: message
)}
{:error, exception} when is_exception(exception) ->
{:error, exception}
end
end
defp prevent_hijacking(_dsl_state, %{prevent_hijacking?: false}), do: :ok
defp prevent_hijacking(_dsl_state, %{registration_enabled?: false}), do: :ok
defp prevent_hijacking(dsl_state, strategy) do
if could_be_hijacked?(dsl_state) && !has_confirmation_add_on?(dsl_state, strategy) do
{:error,
DslError.exception(
path: [:authentication, :strategies, strategy.name],
message: """
If you have a magic strategy and a password strategy that support registration and share an identity field,
you must also have a confirmation add-on that monitors that identity field.
This is to prevent from account hijacking.
For more information, see the confirmation tutorial on hexdocs.
"""
)}
else
:ok
end
end
defp could_be_hijacked?(dsl_state) do
Enum.any?(AshAuthentication.Info.authentication_strategies(dsl_state), fn other_strategy ->
other_strategy.__struct__ == AshAuthentication.Strategy.Password &&
other_strategy.registration_enabled?
end)
end
defp has_confirmation_add_on?(dsl_state, strategy) do
Enum.any?(AshAuthentication.Info.authentication_add_ons(dsl_state), fn add_on ->
add_on.__struct__ == AshAuthentication.AddOn.Confirmation &&
strategy.identity_field in add_on.monitor_fields
end)
end
end