feat: Add option to store all tokens when they're created. (#91)

This commit is contained in:
James Harton 2022-12-14 15:06:13 +13:00 committed by GitHub
parent 719826d66d
commit f1cd72407a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 240 additions and 53 deletions

View file

@ -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
] ]

View file

@ -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)

View file

@ -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: """

View file

@ -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.
""" """

View file

@ -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: [

View file

@ -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

View 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

View file

@ -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,

View file

@ -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]},

View file

@ -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"},

View file

@ -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)

View file

@ -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