mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-19 21:03:23 +12:00
feat: Add option to store all tokens when they're created. (#91)
This commit is contained in:
parent
719826d66d
commit
f1cd72407a
12 changed files with 240 additions and 53 deletions
|
@ -1,24 +1,80 @@
|
||||||
spark_locals_without_parens = [
|
spark_locals_without_parens = [
|
||||||
|
access_token_attribute_name: 1,
|
||||||
|
access_token_expires_at_attribute_name: 1,
|
||||||
api: 1,
|
api: 1,
|
||||||
|
auth_method: 1,
|
||||||
|
authorization_params: 1,
|
||||||
|
authorize_path: 1,
|
||||||
|
client_id: 1,
|
||||||
|
client_secret: 1,
|
||||||
|
confirm_action_name: 1,
|
||||||
|
confirm_on_create?: 1,
|
||||||
|
confirm_on_update?: 1,
|
||||||
|
confirmation: 1,
|
||||||
|
confirmation: 2,
|
||||||
confirmation_required?: 1,
|
confirmation_required?: 1,
|
||||||
|
confirmed_at_field: 1,
|
||||||
|
destroy_action_name: 1,
|
||||||
|
enabled?: 1,
|
||||||
|
expunge_expired_action_name: 1,
|
||||||
|
expunge_interval: 1,
|
||||||
|
get_by_subject_action_name: 1,
|
||||||
|
get_changes_action_name: 1,
|
||||||
hash_provider: 1,
|
hash_provider: 1,
|
||||||
hashed_password_field: 1,
|
hashed_password_field: 1,
|
||||||
identity_field: 1,
|
identity_field: 1,
|
||||||
|
identity_relationship_name: 1,
|
||||||
|
identity_relationship_user_id_attribute: 1,
|
||||||
|
identity_resource: 1,
|
||||||
|
inhibit_updates?: 1,
|
||||||
|
is_revoked_action_name: 1,
|
||||||
|
monitor_fields: 1,
|
||||||
|
oauth2: 1,
|
||||||
|
oauth2: 2,
|
||||||
|
password: 1,
|
||||||
|
password: 2,
|
||||||
password_confirmation_field: 1,
|
password_confirmation_field: 1,
|
||||||
password_field: 1,
|
password_field: 1,
|
||||||
|
password_reset_action_name: 1,
|
||||||
|
private_key: 1,
|
||||||
read_action_name: 1,
|
read_action_name: 1,
|
||||||
|
read_expired_action_name: 1,
|
||||||
|
redirect_uri: 1,
|
||||||
|
refresh_token_attribute_name: 1,
|
||||||
register_action_name: 1,
|
register_action_name: 1,
|
||||||
|
registration_enabled?: 1,
|
||||||
|
request_password_reset_action_name: 1,
|
||||||
|
resettable: 0,
|
||||||
|
resettable: 1,
|
||||||
|
revoke_token_action_name: 1,
|
||||||
|
sender: 1,
|
||||||
sign_in_action_name: 1,
|
sign_in_action_name: 1,
|
||||||
subject_name: 1
|
signing_algorithm: 1,
|
||||||
|
signing_secret: 1,
|
||||||
|
site: 1,
|
||||||
|
store_all_tokens?: 1,
|
||||||
|
store_changes_action_name: 1,
|
||||||
|
strategy_attribute_name: 1,
|
||||||
|
subject_name: 1,
|
||||||
|
token_lifetime: 1,
|
||||||
|
token_path: 1,
|
||||||
|
token_resource: 1,
|
||||||
|
uid_attribute_name: 1,
|
||||||
|
upsert_action_name: 1,
|
||||||
|
user_id_attribute_name: 1,
|
||||||
|
user_path: 1,
|
||||||
|
user_relationship_name: 1,
|
||||||
|
user_resource: 1
|
||||||
]
|
]
|
||||||
|
|
||||||
[
|
[
|
||||||
import_deps: [:ash, :spark],
|
import_deps: [:ash, :spark, :ash_json_api, :ash_graphql],
|
||||||
inputs: [
|
inputs: [
|
||||||
"*.{ex,exs}",
|
"*.{ex,exs}",
|
||||||
"{dev,config,lib,test}/**/*.{ex,exs}"
|
"{dev,config,lib,test}/**/*.{ex,exs}"
|
||||||
],
|
],
|
||||||
plugins: [Spark.Formatter],
|
plugins: [Spark.Formatter],
|
||||||
|
locals_without_parens: spark_locals_without_parens,
|
||||||
export: [
|
export: [
|
||||||
locals_without_parens: spark_locals_without_parens
|
locals_without_parens: spark_locals_without_parens
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,7 +19,7 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
Attempt to confirm a user.
|
Attempt to confirm a user.
|
||||||
"""
|
"""
|
||||||
@spec confirm(Confirmation.t(), map, keyword) :: {:ok, Resource.record()} | {:error, any}
|
@spec confirm(Confirmation.t(), map, keyword) :: {:ok, Resource.record()} | {:error, any}
|
||||||
def confirm(strategy, params, options) do
|
def confirm(strategy, params, opts \\ []) do
|
||||||
with {:ok, api} <- Info.authentication_api(strategy.resource),
|
with {:ok, api} <- Info.authentication_api(strategy.resource),
|
||||||
{:ok, token} <- Map.fetch(params, "confirm"),
|
{:ok, token} <- Map.fetch(params, "confirm"),
|
||||||
{:ok, %{"sub" => subject}, _} <- Jwt.verify(token, strategy.resource),
|
{:ok, %{"sub" => subject}, _} <- Jwt.verify(token, strategy.resource),
|
||||||
|
@ -27,7 +27,7 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
user
|
user
|
||||||
|> Changeset.new()
|
|> Changeset.new()
|
||||||
|> Changeset.for_update(strategy.confirm_action_name, params)
|
|> Changeset.for_update(strategy.confirm_action_name, params)
|
||||||
|> api.update(options)
|
|> api.update(opts)
|
||||||
else
|
else
|
||||||
:error -> {:error, InvalidToken.exception(type: :confirmation)}
|
:error -> {:error, InvalidToken.exception(type: :confirmation)}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
|
@ -37,8 +37,8 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
@doc """
|
@doc """
|
||||||
Store changes in the tokens resource for later re-use.
|
Store changes in the tokens resource for later re-use.
|
||||||
"""
|
"""
|
||||||
@spec store_changes(Confirmation.t(), String.t(), Changeset.t()) :: :ok | {:error, any}
|
@spec store_changes(Confirmation.t(), String.t(), Changeset.t(), keyword) :: :ok | {:error, any}
|
||||||
def store_changes(strategy, token, changeset) do
|
def store_changes(strategy, token, changeset, opts \\ []) do
|
||||||
changes =
|
changes =
|
||||||
strategy.monitor_fields
|
strategy.monitor_fields
|
||||||
|> Stream.filter(&Changeset.changing_attribute?(changeset, &1))
|
|> Stream.filter(&Changeset.changing_attribute?(changeset, &1))
|
||||||
|
@ -57,7 +57,7 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
extra_data: changes,
|
extra_data: changes,
|
||||||
purpose: to_string(strategy.name)
|
purpose: to_string(strategy.name)
|
||||||
})
|
})
|
||||||
|> api.create() do
|
|> api.create(Keyword.merge(opts, upsert?: true)) do
|
||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
|
@ -74,8 +74,8 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
@doc """
|
@doc """
|
||||||
Get changes from the tokens resource for application.
|
Get changes from the tokens resource for application.
|
||||||
"""
|
"""
|
||||||
@spec get_changes(Confirmation.t(), String.t()) :: {:ok, map} | :error
|
@spec get_changes(Confirmation.t(), String.t(), keyword) :: {:ok, map} | :error
|
||||||
def get_changes(strategy, jti) do
|
def get_changes(strategy, jti, opts \\ []) do
|
||||||
with {:ok, token_resource} <- Info.authentication_tokens_token_resource(strategy.resource),
|
with {:ok, token_resource} <- Info.authentication_tokens_token_resource(strategy.resource),
|
||||||
{:ok, api} <- TokenResource.Info.token_api(token_resource),
|
{:ok, api} <- TokenResource.Info.token_api(token_resource),
|
||||||
{:ok, get_changes_action} <-
|
{:ok, get_changes_action} <-
|
||||||
|
@ -85,7 +85,7 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
|
||||||
|> Query.new()
|
|> Query.new()
|
||||||
|> Query.set_context(%{strategy: strategy})
|
|> Query.set_context(%{strategy: strategy})
|
||||||
|> Query.for_read(get_changes_action, %{"jti" => jti})
|
|> Query.for_read(get_changes_action, %{"jti" => jti})
|
||||||
|> api.read() do
|
|> api.read(opts) do
|
||||||
changes =
|
changes =
|
||||||
strategy.monitor_fields
|
strategy.monitor_fields
|
||||||
|> Stream.map(&to_string/1)
|
|> Stream.map(&to_string/1)
|
||||||
|
|
|
@ -116,6 +116,17 @@ defmodule AshAuthentication.Dsl do
|
||||||
""",
|
""",
|
||||||
default: false
|
default: false
|
||||||
],
|
],
|
||||||
|
store_all_tokens?: [
|
||||||
|
type: :boolean,
|
||||||
|
doc: """
|
||||||
|
Store all tokens in the `token_resource`?
|
||||||
|
|
||||||
|
Some applications need to keep track of all tokens issued to
|
||||||
|
any user. This is optional behaviour with `ash_authentication`
|
||||||
|
in order to preserve as much performance as possible.
|
||||||
|
""",
|
||||||
|
default: false
|
||||||
|
],
|
||||||
signing_algorithm: [
|
signing_algorithm: [
|
||||||
type: :string,
|
type: :string,
|
||||||
doc: """
|
doc: """
|
||||||
|
|
|
@ -56,7 +56,7 @@ defmodule AshAuthentication.Jwt do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Ash.Resource
|
alias Ash.Resource
|
||||||
alias AshAuthentication.{Info, Jwt.Config}
|
alias AshAuthentication.{Info, Jwt.Config, TokenResource}
|
||||||
|
|
||||||
@typedoc """
|
@typedoc """
|
||||||
A string likely to contain a valid JWT.
|
A string likely to contain a valid JWT.
|
||||||
|
@ -104,12 +104,27 @@ defmodule AshAuthentication.Jwt do
|
||||||
:error -> extra_claims
|
:error -> extra_claims
|
||||||
end
|
end
|
||||||
|
|
||||||
case Joken.generate_and_sign(default_claims, extra_claims, signer) do
|
with {:ok, token, claims} <- Joken.generate_and_sign(default_claims, extra_claims, signer),
|
||||||
{:ok, token, claims} -> {:ok, token, claims}
|
:ok <- maybe_store_token(token, resource) do
|
||||||
|
{:ok, token, claims}
|
||||||
|
else
|
||||||
{:error, _reason} -> :error
|
{:error, _reason} -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_store_token(token, resource) do
|
||||||
|
if Info.authentication_tokens_store_all_tokens?(resource) do
|
||||||
|
with {:ok, token_resource} <- Info.authentication_tokens_token_resource(resource) do
|
||||||
|
TokenResource.Actions.store_token(token_resource, %{
|
||||||
|
"token" => token,
|
||||||
|
"purpose" => "generic"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Given a token, read it's claims without validating.
|
Given a token, read it's claims without validating.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -38,6 +38,15 @@ defmodule AshAuthentication.TokenResource do
|
||||||
How often to scan this resource for records which have expired, and thus can be removed.
|
How often to scan this resource for records which have expired, and thus can be removed.
|
||||||
""",
|
""",
|
||||||
default: @default_expunge_interval_hrs
|
default: @default_expunge_interval_hrs
|
||||||
|
],
|
||||||
|
store_token_action_name: [
|
||||||
|
type: :atom,
|
||||||
|
doc: """
|
||||||
|
The name of the action to use to store a token.
|
||||||
|
|
||||||
|
Used it `store_all_tokens?` is enabled in your authentication resource.
|
||||||
|
""",
|
||||||
|
default: :store_token
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
sections: [
|
sections: [
|
||||||
|
|
|
@ -112,7 +112,35 @@ defmodule AshAuthentication.TokenResource.Actions do
|
||||||
{:ok, revoke_token_action_name} <-
|
{:ok, revoke_token_action_name} <-
|
||||||
Info.token_revocation_revoke_token_action_name(resource) do
|
Info.token_revocation_revoke_token_action_name(resource) do
|
||||||
resource
|
resource
|
||||||
|> Changeset.for_create(revoke_token_action_name, %{"token" => token}, opts)
|
|> Changeset.for_create(
|
||||||
|
revoke_token_action_name,
|
||||||
|
%{"token" => token},
|
||||||
|
Keyword.merge(opts, upsert?: true)
|
||||||
|
)
|
||||||
|
|> api.create()
|
||||||
|
|> 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, api} <- Info.token_api(resource),
|
||||||
|
{:ok, store_token_action_name} <- Info.token_store_token_action_name(resource) do
|
||||||
|
resource
|
||||||
|
|> Changeset.for_create(
|
||||||
|
store_token_action_name,
|
||||||
|
params,
|
||||||
|
Keyword.merge(opts, upsert?: true)
|
||||||
|
)
|
||||||
|> api.create()
|
|> api.create()
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, _} -> :ok
|
{:ok, _} -> :ok
|
||||||
|
|
27
lib/ash_authentication/token_resource/store_token_change.ex
Normal file
27
lib/ash_authentication/token_resource/store_token_change.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule AshAuthentication.TokenResource.StoreTokenChange do
|
||||||
|
@moduledoc """
|
||||||
|
Stores an arbitrary token.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ash.Resource.Change
|
||||||
|
alias Ash.{Changeset, Error.Changes.InvalidArgument, Resource.Change}
|
||||||
|
alias AshAuthentication.Jwt
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
@spec change(Changeset.t(), keyword, Change.context()) :: Changeset.t()
|
||||||
|
def change(changeset, _opts, _context) do
|
||||||
|
with token when byte_size(token) > 0 <- Changeset.get_argument(changeset, :token),
|
||||||
|
{:ok, %{"jti" => jti, "exp" => exp}} <- Jwt.peek(token),
|
||||||
|
{:ok, expires_at} <- DateTime.from_unix(exp) do
|
||||||
|
changeset
|
||||||
|
|> Changeset.change_attributes(jti: jti, expires_at: expires_at)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
changeset
|
||||||
|
|> Changeset.add_error([
|
||||||
|
InvalidArgument.exception(field: :token, message: "is not a valid token")
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -131,11 +131,51 @@ defmodule AshAuthentication.TokenResource.Transformer do
|
||||||
validate_store_confirmation_changes_action(
|
validate_store_confirmation_changes_action(
|
||||||
dsl_state,
|
dsl_state,
|
||||||
store_confirmation_changes_action_name
|
store_confirmation_changes_action_name
|
||||||
) do
|
),
|
||||||
|
{:ok, store_token_action_name} <-
|
||||||
|
Info.token_store_token_action_name(dsl_state),
|
||||||
|
{:ok, dsl_state} <-
|
||||||
|
maybe_build_action(
|
||||||
|
dsl_state,
|
||||||
|
store_token_action_name,
|
||||||
|
&build_store_token_action(&1, store_token_action_name)
|
||||||
|
),
|
||||||
|
:ok <- validate_store_token_action(dsl_state, store_token_action_name) do
|
||||||
{:ok, dsl_state}
|
{:ok, dsl_state}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_store_token_action(dsl_state, action_name) do
|
||||||
|
with {:ok, action} <- validate_action_exists(dsl_state, action_name),
|
||||||
|
:ok <- validate_token_argument(action) do
|
||||||
|
validate_action_has_change(action, TokenResource.StoreTokenChange)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_store_token_action(_dsl_state, action_name) do
|
||||||
|
arguments = [
|
||||||
|
Transformer.build_entity!(Resource.Dsl, [:actions, :create], :argument,
|
||||||
|
name: :token,
|
||||||
|
type: :string,
|
||||||
|
allow_nil?: false,
|
||||||
|
sensitive?: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
changes = [
|
||||||
|
Transformer.build_entity!(Resource.Dsl, [:actions, :create], :change,
|
||||||
|
change: TokenResource.StoreTokenChange
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
Transformer.build_entity(Resource.Dsl, [:actions], :create,
|
||||||
|
name: action_name,
|
||||||
|
arguments: arguments,
|
||||||
|
changes: changes,
|
||||||
|
accept: [:extra_data, :purpose]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp build_store_confirmation_changes_action(_dsl_state, action_name) do
|
defp build_store_confirmation_changes_action(_dsl_state, action_name) do
|
||||||
arguments = [
|
arguments = [
|
||||||
Transformer.build_entity!(Resource.Dsl, [:actions, :create], :argument,
|
Transformer.build_entity!(Resource.Dsl, [:actions, :create], :argument,
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -133,7 +133,7 @@ defmodule AshAuthentication.MixProject do
|
||||||
{:absinthe_plug, "~> 1.5", only: [:dev, :test]},
|
{:absinthe_plug, "~> 1.5", only: [:dev, :test]},
|
||||||
{:ash_graphql, "~> 0.21", only: [:dev, :test]},
|
{:ash_graphql, "~> 0.21", only: [:dev, :test]},
|
||||||
{:ash_json_api, "~> 0.30", only: [:dev, :test]},
|
{:ash_json_api, "~> 0.30", only: [:dev, :test]},
|
||||||
{:ash_postgres, "~> 1.1", only: [:dev, :test]},
|
{:ash_postgres, "~> 1.2.1", only: [:dev, :test]},
|
||||||
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
||||||
{:dialyxir, "~> 1.2", only: [:dev, :test], runtime: false},
|
{:dialyxir, "~> 1.2", only: [:dev, :test], runtime: false},
|
||||||
{:doctor, "~> 0.18", only: [:dev, :test]},
|
{:doctor, "~> 0.18", only: [:dev, :test]},
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -4,7 +4,7 @@
|
||||||
"ash": {:hex, :ash, "2.4.24", "fb74aaf9ee8d9c8397c1c57d2d2ebf48cc0b3a933736eab66b25cea72826ac9f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 0.2.18", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e1b7ac0cf41af75f954bfb9500d1aeb54e4b3b34ec41dbe71c685b92ef8304ae"},
|
"ash": {:hex, :ash, "2.4.24", "fb74aaf9ee8d9c8397c1c57d2d2ebf48cc0b3a933736eab66b25cea72826ac9f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 0.2.18", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e1b7ac0cf41af75f954bfb9500d1aeb54e4b3b34ec41dbe71c685b92ef8304ae"},
|
||||||
"ash_graphql": {:hex, :ash_graphql, "0.22.2", "35f49776864e0d7abfd7cdded61d6b600e63792cad77cac24475c3606d2ea85e", [:mix], [{:absinthe, "~> 1.7", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:ash, "~> 2.4", [hex: :ash, repo: "hexpm", optional: false]}, {:dataloader, "~> 1.0", [hex: :dataloader, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fee44d457986587617d5ad11b262594a1ed689c81861f255dabbe628f1cde877"},
|
"ash_graphql": {:hex, :ash_graphql, "0.22.2", "35f49776864e0d7abfd7cdded61d6b600e63792cad77cac24475c3606d2ea85e", [:mix], [{:absinthe, "~> 1.7", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:ash, "~> 2.4", [hex: :ash, repo: "hexpm", optional: false]}, {:dataloader, "~> 1.0", [hex: :dataloader, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fee44d457986587617d5ad11b262594a1ed689c81861f255dabbe628f1cde877"},
|
||||||
"ash_json_api": {:hex, :ash_json_api, "0.30.1", "54e60c4862eee35ed8a9a925e5c99be2b80e36a2507355bdb0f0974defe82a8d", [:mix], [{:ash, "~> 2.0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b8b4827aa02de75a9a48d2941e813947da46b7dbfdd84cd20959dbaec103f830"},
|
"ash_json_api": {:hex, :ash_json_api, "0.30.1", "54e60c4862eee35ed8a9a925e5c99be2b80e36a2507355bdb0f0974defe82a8d", [:mix], [{:ash, "~> 2.0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b8b4827aa02de75a9a48d2941e813947da46b7dbfdd84cd20959dbaec103f830"},
|
||||||
"ash_postgres": {:hex, :ash_postgres, "1.1.3", "d025ec75a0c64dccc58ff0fce7ec4a8a800f59aab1723c29dda28c2f9f068357", [:mix], [{:ash, "~> 2.1", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "59d92075052defe30744ee7eb721da3ffcb2cd8d8081766af2d6cd70619274a9"},
|
"ash_postgres": {:hex, :ash_postgres, "1.2.1", "b4dfcda819e18e64c5d97bb23589e2d248f2fae303309aa766b46ebfdd655ae9", [:mix], [{:ash, ">= 2.4.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "824441b3b01cada477a5a81d08aa4409b365bb447720132edde65679da2f6a2b"},
|
||||||
"assent": {:hex, :assent, "0.2.1", "46ad0ed92b72330f38c60bc03c528e8408475dc386f48d4ecd18833cfa581b9f", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "58c558b6029ffa287e15b38c8e07cd99f0b24e4846c52abad0c0a6225c4873bc"},
|
"assent": {:hex, :assent, "0.2.1", "46ad0ed92b72330f38c60bc03c528e8408475dc386f48d4ecd18833cfa581b9f", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "58c558b6029ffa287e15b38c8e07cd99f0b24e4846c52abad0c0a6225c4873bc"},
|
||||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
|
||||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||||
|
|
|
@ -100,7 +100,7 @@ defmodule AshAuthentication.AddOn.Confirmation.ActionsTest do
|
||||||
},
|
},
|
||||||
~w[jti expires_at purpose extra_data]a
|
~w[jti expires_at purpose extra_data]a
|
||||||
)
|
)
|
||||||
|> Example.Repo.insert!()
|
|> Example.Repo.insert!(on_conflict: :replace_all, conflict_target: :jti)
|
||||||
|
|
||||||
{:ok, changes} = Actions.get_changes(strategy, jti)
|
{:ok, changes} = Actions.get_changes(strategy, jti)
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ defmodule Example.User do
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
uuid_primary_key(:id, writable?: true)
|
uuid_primary_key :id, writable?: true
|
||||||
|
|
||||||
attribute(:username, :ci_string, allow_nil?: false)
|
attribute :username, :ci_string, allow_nil?: false
|
||||||
attribute(:hashed_password, :string, allow_nil?: true, sensitive?: true, private?: true)
|
attribute :hashed_password, :string, allow_nil?: true, sensitive?: true, private?: true
|
||||||
|
|
||||||
create_timestamp(:created_at)
|
create_timestamp :created_at
|
||||||
update_timestamp(:updated_at)
|
update_timestamp :updated_at
|
||||||
end
|
end
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
|
@ -70,9 +70,9 @@ defmodule Example.User do
|
||||||
type :user
|
type :user
|
||||||
|
|
||||||
queries do
|
queries do
|
||||||
get(:get_user, :read)
|
get :get_user, :read
|
||||||
list(:list_users, :read)
|
list :list_users, :read
|
||||||
read_one(:current_user, :current_user)
|
read_one :current_user, :current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
mutations do
|
mutations do
|
||||||
|
@ -84,67 +84,68 @@ defmodule Example.User do
|
||||||
type "user"
|
type "user"
|
||||||
|
|
||||||
routes do
|
routes do
|
||||||
base("/users")
|
base "/users"
|
||||||
get(:read)
|
get :read
|
||||||
get(:current_user, route: "/me")
|
get :current_user, route: "/me"
|
||||||
index(:read)
|
index :read
|
||||||
post(:register_with_password)
|
post :register_with_password
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
postgres do
|
postgres do
|
||||||
table("user")
|
table "user"
|
||||||
repo(Example.Repo)
|
repo(Example.Repo)
|
||||||
end
|
end
|
||||||
|
|
||||||
authentication do
|
authentication do
|
||||||
api(Example)
|
api Example
|
||||||
|
|
||||||
tokens do
|
tokens do
|
||||||
enabled?(true)
|
enabled? true
|
||||||
token_resource(Example.Token)
|
store_all_tokens? true
|
||||||
signing_secret(&get_config/2)
|
token_resource Example.Token
|
||||||
|
signing_secret &get_config/2
|
||||||
end
|
end
|
||||||
|
|
||||||
add_ons do
|
add_ons do
|
||||||
confirmation :confirm do
|
confirmation :confirm do
|
||||||
monitor_fields([:username])
|
monitor_fields [:username]
|
||||||
inhibit_updates?(true)
|
inhibit_updates? true
|
||||||
|
|
||||||
sender(fn user, token ->
|
sender fn user, token ->
|
||||||
Logger.debug("Confirmation request for user #{user.username}, token #{inspect(token)}")
|
Logger.debug("Confirmation request for user #{user.username}, token #{inspect(token)}")
|
||||||
end)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
strategies do
|
strategies do
|
||||||
password :password do
|
password :password do
|
||||||
resettable do
|
resettable do
|
||||||
sender(fn user, token ->
|
sender fn user, token ->
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"Password reset request for user #{user.username}, token #{inspect(token)}"
|
"Password reset request for user #{user.username}, token #{inspect(token)}"
|
||||||
)
|
)
|
||||||
end)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
oauth2 :oauth2 do
|
oauth2 :oauth2 do
|
||||||
client_id(&get_config/2)
|
client_id &get_config/2
|
||||||
redirect_uri(&get_config/2)
|
redirect_uri &get_config/2
|
||||||
client_secret(&get_config/2)
|
client_secret &get_config/2
|
||||||
site(&get_config/2)
|
site &get_config/2
|
||||||
authorize_path(&get_config/2)
|
authorize_path &get_config/2
|
||||||
token_path(&get_config/2)
|
token_path &get_config/2
|
||||||
user_path(&get_config/2)
|
user_path &get_config/2
|
||||||
authorization_params(scope: "openid profile email")
|
authorization_params scope: "openid profile email"
|
||||||
auth_method(:client_secret_post)
|
auth_method :client_secret_post
|
||||||
identity_resource(Example.UserIdentity)
|
identity_resource Example.UserIdentity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
identities do
|
identities do
|
||||||
identity(:username, [:username], eager_check_with: Example)
|
identity :username, [:username], eager_check_with: Example
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_config(path, _resource) do
|
def get_config(path, _resource) do
|
||||||
|
|
Loading…
Reference in a new issue