Packages
ash_authentication
4.8.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/api_key/sign_in_preparation.ex
defmodule AshAuthentication.Strategy.ApiKey.SignInPreparation do
@moduledoc """
Prepare a query for sign in.
"""
use Ash.Resource.Preparation
alias AshAuthentication.{Errors.AuthenticationFailed, Info}
alias Ash.{Query, Resource.Preparation}
require Ash.Query
@doc false
@impl true
@spec prepare(Query.t(), keyword, Preparation.Context.t()) :: Query.t()
def prepare(query, _opts, _context) do
with {:ok, strategy} <- Info.strategy_for_action(query.resource, query.action.name),
{:ok, api_key} <- Query.fetch_argument(query, :api_key),
{:ok, api_key_id, random_bytes} <- decode_api_key(api_key) do
api_key_relationship =
Ash.Resource.Info.relationship(query.resource, strategy.api_key_relationship)
query
|> Query.set_context(%{private: %{ash_authentication?: true}})
|> Ash.Query.before_action(fn query ->
api_key_relationship.destination
|> Ash.Query.do_filter(api_key_relationship.filter)
|> Ash.Query.filter(id == ^api_key_id)
|> Query.set_context(%{private: %{ash_authentication?: true}})
|> Ash.read_one()
|> case do
{:ok, nil} ->
Plug.Crypto.secure_compare(
:crypto.hash(:sha256, random_bytes <> api_key_id),
Ecto.UUID.bingenerate() <> :crypto.strong_rand_bytes(32)
)
Ash.Query.filter(query, false)
{:ok, api_key} ->
check_api_key(
query,
api_key,
api_key_id,
strategy,
api_key_relationship,
random_bytes
)
{:error, error} ->
Ash.Query.add_error(
query,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: error
)
)
end
end)
else
_ ->
Plug.Crypto.secure_compare(
:crypto.hash(:sha256, :crypto.strong_rand_bytes(32) <> Ecto.UUID.bingenerate()),
Ecto.UUID.bingenerate() <> :crypto.strong_rand_bytes(32)
)
Query.do_filter(query, false)
end
end
defp check_api_key(query, api_key, api_key_id, strategy, api_key_relationship, random_bytes) do
if Plug.Crypto.secure_compare(
:crypto.hash(:sha256, random_bytes <> api_key_id),
Map.get(api_key, strategy.api_key_hash_attribute)
) do
query
|> Ash.Query.do_filter(%{
api_key_relationship.source_attribute =>
Map.get(api_key, api_key_relationship.destination_attribute)
})
|> Ash.Query.after_action(fn
_query, [user] ->
{:ok,
[
Ash.Resource.set_metadata(
user,
%{
api_key: api_key,
using_api_key?: true
}
)
]}
query, [] ->
{:error,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Query returned no users"
}
)}
query, _ ->
{:error,
AuthenticationFailed.exception(
strategy: strategy,
query: query,
caused_by: %{
module: __MODULE__,
strategy: strategy,
action: :sign_in,
message: "Query returned too many users"
}
)}
end)
else
Ash.Query.filter(query, false)
end
end
defp decode_api_key(api_key) do
with [_parse_prefix, middle, crc32] <- String.split(api_key, "_", parts: 3),
{:ok, <<random_bytes::binary-size(32), id::binary-size(16)>>} <-
AshAuthentication.Base.bindecode62(middle),
true <-
AshAuthentication.Base.decode62(crc32) ==
{:ok, :erlang.crc32(random_bytes <> id)} do
{:ok, id, random_bytes}
else
_ ->
:error
end
end
end