Packages
ash_authentication
4.14.1
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/strategies/remember_me/sign_in_preparation.ex
# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT
defmodule AshAuthentication.Strategy.RememberMe.SignInPreparation do
@moduledoc """
Prepare a query for sign in via the remember me token.
This preparation first validates the token argument and extracts the subject
from it and constrains the query to a matching user.
"""
use Ash.Resource.Preparation
alias Ash.{Query, Resource, Resource.Preparation}
alias AshAuthentication.{Errors.AuthenticationFailed, Info, Jwt}
require Ash.Query
@doc false
@impl true
@spec prepare(Query.t(), keyword, Preparation.Context.t()) :: Query.t()
def prepare(query, options, context) do
{:ok, strategy} = Info.find_strategy(query, context, options)
query
|> Query.before_action(&verify_token_and_constrain_query(&1, strategy, context))
|> Query.after_action(&verify_result(&1, &2, strategy, context))
end
defp verify_token_and_constrain_query(query, strategy, context) do
token = Query.get_argument(query, :token)
with {:ok, claims, _} <-
Jwt.verify(token, strategy.resource, Ash.Context.to_opts(context)),
:ok <- verify_token_purpose(claims),
{:ok, primary_keys} <- extract_primary_keys_from_subject(claims, strategy.resource) do
query
|> Query.filter(^primary_keys)
|> Query.put_context(:token_claims, claims)
else
:error ->
Query.add_error(
query,
[:token],
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
action: query.action,
resource: query.resource,
message: "The token is invalid"
}
)
)
{:error, reason} ->
Query.add_error(
query,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
action: query.action,
resource: query.resource,
message: reason
}
)
)
end
end
defp verify_result(query, [user], strategy, context) do
claims =
query.context
|> Map.get(:token_claims, %{})
|> Map.take(["tenant"])
case Jwt.token_for_user(user, claims, Ash.Context.to_opts(context)) do
{:ok, token, _claims} ->
{:ok, [Resource.put_metadata(user, :token, token)]}
:error ->
{:error,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
action: query.action,
resource: query.resource,
message: "Unable to generate token for user"
}
)}
end
end
defp verify_result(query, [], strategy, _context) do
{:error,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
action: query.action,
resource: query.resource,
message: "Query returned no users"
}
)}
end
defp verify_result(query, users, strategy, _context) when is_list(users) do
{:error,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
action: query.action,
resource: query.resource,
message: "Query returned too many users"
}
)}
end
defp verify_token_purpose(%{"purpose" => "remember_me"}), do: :ok
defp verify_token_purpose(_), do: {:error, "The token purpose is not valid"}
defp extract_primary_keys_from_subject(%{"sub" => sub}, resource) do
primary_key_fields =
resource
|> Resource.Info.primary_key()
|> Enum.map(&to_string/1)
|> MapSet.new()
key_parts =
sub
|> URI.parse()
|> Map.get(:query, "")
|> URI.decode_query()
provided_key_fields =
key_parts
|> Map.keys()
|> MapSet.new()
if MapSet.equal?(primary_key_fields, provided_key_fields) do
{:ok, Enum.to_list(key_parts)}
else
{:error, "token subject doesn't contain correct keys"}
end
end
defp extract_primary_keys_from_subject(_, _),
do: {:error, "The token does not contain a subject"}
end