Packages
ash_authentication
4.2.4
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/token_resource/actions.ex
defmodule AshAuthentication.TokenResource.Actions do
@moduledoc """
The code interface for interacting with the token resource.
"""
alias Ash.{Changeset, DataLayer, Notifier, Query, Resource}
alias AshAuthentication.{TokenResource, TokenResource.Info}
import AshAuthentication.Utils
@doc false
@spec read_expired(Resource.t(), keyword) :: {:ok, [Resource.record()]} | {:error, any}
def read_expired(resource, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, read_expired_action_name} <- Info.token_read_expired_action_name(resource) do
resource
|> Query.new()
|> Query.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Query.for_read(read_expired_action_name, opts)
|> Ash.read(domain: domain)
end
end
@doc """
Remove all expired records.
"""
@spec expunge_expired(Resource.t(), keyword) :: :ok | {:error, any}
def expunge_expired(resource, opts \\ []) do
expunge_expired_action_name = Info.token_expunge_expired_action_name!(resource)
resource
|> DataLayer.transaction(
fn -> expunge_inside_transaction(resource, expunge_expired_action_name, opts) end,
nil,
%{
type: :bulk_destroy,
metadata: %{
metadata: %{
resource: resource,
action: expunge_expired_action_name
}
}
}
)
|> case do
{:ok, {:ok, notifications}} ->
Notifier.notify(notifications)
:ok
{:ok, {:error, reason}} ->
{:error, reason}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Has the token been revoked?
Similar to `jti_revoked?/2..3` except that it extracts the JTI from the token,
rather than relying on it to be passed in.
"""
@spec token_revoked?(Resource.t(), String.t(), keyword) :: boolean
def token_revoked?(resource, token, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, is_revoked_action_name} <- Info.token_revocation_is_revoked_action_name(resource),
action when not is_nil(action) <-
Ash.Resource.Info.action(resource, is_revoked_action_name) do
case action.type do
:action ->
resource
|> Ash.ActionInput.for_action(
is_revoked_action_name,
%{"token" => token},
Keyword.put(opts, :domain, domain)
)
|> Ash.run_action()
|> case do
{:ok, value} -> value
_ -> false
end
:read ->
resource
|> Query.new()
|> Query.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Query.for_read(
is_revoked_action_name,
%{"token" => token},
Keyword.put(opts, :domain, domain)
)
|> Ash.read()
|> case do
{:ok, []} -> false
{:ok, _} -> true
_ -> false
end
end
end
end
@doc """
Has the token been revoked?
Similar to `token-revoked?/2..3` except that rather than extracting the JTI
from the token, assumes that it's being passed in directly.
"""
@spec jti_revoked?(Resource.t(), String.t(), keyword) :: boolean
def jti_revoked?(resource, jti, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, is_revoked_action_name} <- Info.token_revocation_is_revoked_action_name(resource),
action when not is_nil(action) <-
Ash.Resource.Info.action(resource, is_revoked_action_name) do
case action.type do
:action ->
resource
|> Ash.ActionInput.for_action(
is_revoked_action_name,
%{"jti" => jti},
Keyword.put(opts, :domain, domain)
)
|> Ash.run_action()
|> case do
{:ok, value} -> value
_ -> false
end
:read ->
resource
|> Query.new()
|> Query.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Query.for_read(
is_revoked_action_name,
%{"jti" => jti},
Keyword.put(opts, :domain, domain)
)
|> Ash.read()
|> case do
{:ok, []} -> false
{:ok, _} -> true
_ -> false
end
end
end
end
@doc false
@spec valid_jti?(Resource.t(), String.t(), keyword) :: boolean
def valid_jti?(resource, jti, opts \\ []), do: !jti_revoked?(resource, jti, opts)
@doc """
Revoke a token.
Extracts the JTI from the provided token and uses it to generate a revocation
record.
"""
@spec revoke(Resource.t(), String.t(), keyword) :: :ok | {:error, any}
def revoke(resource, token, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, revoke_token_action_name} <-
Info.token_revocation_revoke_token_action_name(resource) do
resource
|> Changeset.new()
|> Changeset.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Changeset.for_create(
revoke_token_action_name,
%{"token" => token},
Keyword.merge(opts, upsert?: true)
)
|> Ash.create(domain: domain)
|> case do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end
end
@doc """
Store a token.
Stores a token for any purpose.
"""
@spec store_token(Resource.t(), map, keyword) :: :ok | {:error, any}
def store_token(resource, params, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, store_token_action_name} <- Info.token_store_token_action_name(resource) do
resource
|> Changeset.new()
|> Changeset.set_context(%{
private: %{
ash_authentication?: true
}
})
|> Changeset.for_create(
store_token_action_name,
params,
Keyword.merge(opts, upsert?: true)
)
|> Ash.create(domain: domain)
|> case do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end
end
@doc """
Retrieve a token by token or JTI optionally filtering by purpose.
"""
@spec get_token(Resource.t(), map, keyword) :: {:ok, [Resource.record()]} | {:error, any}
def get_token(resource, params, opts \\ []) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, domain} <- Info.token_domain(resource),
{:ok, get_token_action_name} <- Info.token_get_token_action_name(resource) do
resource
|> Query.new()
|> Query.set_context(%{private: %{ash_authentication?: true}})
|> Query.for_read(get_token_action_name, params, opts)
|> Ash.read(domain: domain)
end
end
defp expunge_inside_transaction(resource, expunge_expired_action_name, opts) do
with :ok <- assert_resource_has_extension(resource, TokenResource),
{:ok, read_expired_action_name} <- Info.token_read_expired_action_name(resource) do
opts =
opts
|> Keyword.put_new_lazy(:domain, fn -> Info.token_domain!(resource) end)
resource
|> Query.new()
|> Query.set_context(%{private: %{ash_authentication?: true}})
|> Query.for_read(read_expired_action_name, %{}, opts)
|> Ash.bulk_destroy(
expunge_expired_action_name,
%{},
Keyword.put_new(opts, :strategy, [:atomic, :atomic_batches, :stream])
)
|> case do
%{status: :success, notifications: notifications} -> {:ok, notifications}
%{errors: errors} -> {:error, Ash.Error.to_class(errors)}
end
end
end
end