ash_authentication/lib/ash_authentication/verifier.ex

142 lines
4.1 KiB
Elixir

defmodule AshAuthentication.Verifier do
@moduledoc """
The Authentication verifier.
Checks configuration constraints after compile.
"""
use Spark.Dsl.Verifier
alias AshAuthentication.{Info, Strategy, Strategy.Password}
alias Spark.{Dsl.Transformer, Error.DslError}
import AshAuthentication.Utils
@doc false
@impl true
@spec verify(map) ::
:ok
| {:error, term}
| {:warn, String.t() | list(String.t())}
def verify(dsl_state) do
with {:ok, _domain} <- validate_domain_presence(dsl_state),
:ok <- validate_tokens_may_be_required(dsl_state) do
validate_token_resource(dsl_state)
end
end
defp validate_domain_presence(dsl_state) do
with domain when not is_nil(domain) <-
Transformer.get_option(dsl_state, [:authentication], :domain),
:ok <- assert_is_module(domain),
true <- function_exported?(domain, :spark_is, 0),
Ash.Domain <- domain.spark_is() do
{:ok, domain}
else
nil ->
{:error,
DslError.exception(
path: [:authentication, :domain],
message: "A domain module must be present"
)}
_ ->
{:error,
DslError.exception(
path: [:authentication, :domain],
message: "Module is not an `Ash.Domain`."
)}
end
end
defp validate_tokens_may_be_required(dsl_state) do
strategies_requiring_tokens =
dsl_state
|> Info.authentication_strategies()
|> Enum.filter(&Strategy.tokens_required?/1)
tokens_enabled? =
dsl_state
|> Info.authentication_tokens_enabled?()
case {strategies_requiring_tokens, tokens_enabled?} do
{[], _} ->
:ok
{_, true} ->
:ok
{[password | _], false}
when is_struct(password, Password) and is_map(password.resettable) ->
{:error,
DslError.exception(
path: [:authentication, :tokens, :enabled?],
message: """
The `#{password.name}` password authentication strategy requires tokens be enabled because reset tokens are in use.
To fix this error you can either:
1. disable password resets by removing the `resettable` configuration from your password strategy, or
2. enable tokens.
"""
)}
{[password | _], false}
when is_struct(password, Password) and password.sign_in_tokens_enabled? ->
{:error,
DslError.exception(
path: [:authentication, :tokens, :enabled?],
message: """
The `#{password.name}` password authentication strategy requires tokens be enabled because sign-in tokens are in use.
To fix this error you can either:
1. disable sign in tokens by setting `sign_in_tokens? false` your password strategy, or
2. enable tokens.
"""
)}
{[strategy | _], false} ->
{:error,
DslError.exception(
path: [:authentication, :tokens, :enabled?],
message: """
The `#{inspect(strategy.name)}` authentication strategy requires tokens be enabled.
To fix this error you can either:
1. disable the `#{inspect(strategy.name)}` strategy, or
2. enable tokens.
"""
)}
end
end
defp validate_token_resource(dsl_state) do
if_tokens_enabled(dsl_state, fn dsl_state ->
with {:ok, resource} when is_truthy(resource) <-
Info.authentication_tokens_token_resource(dsl_state),
true <- is_atom(resource) do
:ok
else
{:ok, falsy} when is_falsy(falsy) ->
:ok
{:error, reason} ->
{:error, reason}
false ->
{:error,
DslError.exception(
path: [:authentication, :tokens, :token_resource],
message: "is not a valid module name"
)}
end
end)
end
defp if_tokens_enabled(dsl_state, validator) when is_function(validator, 1) do
if Info.authentication_tokens_enabled?(dsl_state) do
validator.(dsl_state)
else
:ok
end
end
end