mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-19 04:53:56 +12:00
feat(PasswordReset): Add a generic password reset form (#37)
* feat(PasswordReset): Add a generic password reset form * improvement(Input.submit): trim trailing "with password" from submit buttons.
This commit is contained in:
parent
90b0a45363
commit
432f056905
14 changed files with 423 additions and 12 deletions
|
@ -21,7 +21,7 @@ config :ash_authentication_phoenix, DevWeb.Endpoint,
|
|||
|
||||
config :ash_authentication_phoenix, ash_apis: [Example.Accounts], namespace: Dev
|
||||
|
||||
config :ash_authentication, AshAuthentication.Jwt,
|
||||
config :ash_authentication_phoenix,
|
||||
signing_secret: "All I wanna do is to thank you, even though I don't know who you are."
|
||||
|
||||
config :phoenix, :json_library, Jason
|
||||
|
|
|
@ -34,5 +34,6 @@ defmodule DevWeb.Router do
|
|||
auth_routes_for(Example.Accounts.User, to: AuthController, path: "/auth")
|
||||
sign_in_route(overrides: [DevWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default])
|
||||
sign_out_route(AuthController, "/sign-out")
|
||||
reset_route()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -230,13 +230,19 @@ defmodule AshAuthentication.Phoenix.Components.Password.Input do
|
|||
:request_reset ->
|
||||
assigns.strategy.resettable
|
||||
|> Enum.map(& &1.request_password_reset_action_name)
|
||||
|> List.first() || :reqest_reset
|
||||
|> List.first(:request_reset)
|
||||
|> to_string()
|
||||
|> String.trim_trailing("_with_password")
|
||||
|
||||
:sign_in ->
|
||||
assigns.strategy.sign_in_action_name
|
||||
|> to_string()
|
||||
|> String.trim_trailing("_with_password")
|
||||
|
||||
:register ->
|
||||
assigns.strategy.register_action_name
|
||||
|> to_string()
|
||||
|> String.trim_trailing("_with_password")
|
||||
end
|
||||
|> humanize()
|
||||
end)
|
||||
|
|
|
@ -141,7 +141,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do
|
|||
|> Form.validate(params)
|
||||
|> Form.submit()
|
||||
|
||||
flash = override_for(socket, :reset_flash_text)
|
||||
flash = override_for(socket.assigns.overrides, :reset_flash_text)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|
|
84
lib/ash_authentication_phoenix/components/reset.ex
Normal file
84
lib/ash_authentication_phoenix/components/reset.ex
Normal file
|
@ -0,0 +1,84 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.Reset do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
strategy_class: "CSS class for a `div` surrounding each strategy component.",
|
||||
show_banner: "Whether or not to show the banner."
|
||||
|
||||
@moduledoc """
|
||||
Renders a password-reset form.
|
||||
|
||||
## Component heirarchy
|
||||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.password_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.password_confirmation_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.submit/1`
|
||||
|
||||
## Props
|
||||
|
||||
* `token` - The reset token.
|
||||
* `overrides` - A list of override modules.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
alias AshAuthentication.{Info, Phoenix.Components, Strategy.Password}
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import AshAuthentication.Phoenix.Components.Helpers
|
||||
|
||||
@type props :: %{
|
||||
required(:token) => String.t(),
|
||||
optional(:overrides) => [module]
|
||||
}
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec update(props, Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
strategies =
|
||||
socket
|
||||
|> otp_app_from_socket()
|
||||
|> AshAuthentication.authenticated_resources()
|
||||
|> Enum.sort_by(&Info.authentication_subject_name!/1)
|
||||
|> Stream.flat_map(&Info.authentication_strategies/1)
|
||||
|> Stream.filter(&is_struct(&1, Password))
|
||||
|> Enum.filter(&Enum.any?(&1.resettable))
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(strategies: strategies)
|
||||
|> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec render(Socket.assigns()) :: Rendered.t() | no_return
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@overrides, :root_class)}>
|
||||
<%= if override_for(@overrides, :show_banner, true) do %>
|
||||
<.live_component module={Components.Banner} socket={@socket} id="sign-in-banner" />
|
||||
<% end %>
|
||||
|
||||
<%= for strategy <- @strategies do %>
|
||||
<div class={override_for(@overrides, :strategy_class)}>
|
||||
<.live_component
|
||||
module={Components.Reset.Form}
|
||||
strategy={strategy}
|
||||
token={@token}
|
||||
socket={@socket}
|
||||
id="reset-form"
|
||||
label={false}
|
||||
overrides={@overrides}
|
||||
/>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
183
lib/ash_authentication_phoenix/components/reset/form.ex
Normal file
183
lib/ash_authentication_phoenix/components/reset/form.ex
Normal file
|
@ -0,0 +1,183 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.Reset.Form do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
label_class: "CSS class for the `h2` element.",
|
||||
form_class: "CSS class for the `form` element.",
|
||||
spacer_class: "CSS classes for space between the password input and submit elements.",
|
||||
disable_button_text: "Text for the submit button when the request is happening."
|
||||
|
||||
@moduledoc """
|
||||
Generates a default password reset form.
|
||||
|
||||
## Component heirarchy
|
||||
|
||||
This is a child of `AshAuthentication.Phoenix.Components.Reset`.
|
||||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.password_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.submit/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.error/1`
|
||||
|
||||
## Props
|
||||
|
||||
* `token` - The reset token.
|
||||
* `socket` - Phoenix LiveView socket. This is needed to be able to retrieve
|
||||
the correct CSS configuration. Required.
|
||||
* `strategy` - The configuration map as per
|
||||
`AshAuthentication.Info.strategy/2`. Required.
|
||||
* `label` - The text to show in the submit label. Generated from the
|
||||
configured action name (via `Phoenix.HTML.Form.humanize/1`) if not
|
||||
supplied. Set to `false` to disable.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
alias AshAuthentication.{Info, Phoenix.Components.Password.Input}
|
||||
alias AshPhoenix.Form
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import AshAuthentication.Phoenix.Components.Helpers, only: [route_helpers: 1]
|
||||
import Phoenix.HTML.Form
|
||||
import Slug
|
||||
|
||||
@type props :: %{
|
||||
required(:socket) => Socket.t(),
|
||||
required(:strategy) => AshAuthentication.Strategy.t(),
|
||||
required(:token) => String.t(),
|
||||
optional(:label) => String.t() | false,
|
||||
optional(:overrices) => [module]
|
||||
}
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec update(props, Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
strategy = assigns.strategy
|
||||
api = Info.authentication_api!(strategy.resource)
|
||||
subject_name = Info.authentication_subject_name!(strategy.resource)
|
||||
|
||||
[resettable] = strategy.resettable
|
||||
|
||||
form =
|
||||
strategy.resource
|
||||
|> Form.for_action(resettable.password_reset_action_name,
|
||||
api: api,
|
||||
as: subject_name |> to_string(),
|
||||
id:
|
||||
"#{subject_name}-#{strategy.name}-#{resettable.password_reset_action_name}" |> slugify(),
|
||||
context: %{strategy: strategy}
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(
|
||||
form: form,
|
||||
trigger_action: false,
|
||||
subject_name: subject_name,
|
||||
resettable: resettable
|
||||
)
|
||||
|> assign_new(:label, fn -> humanize(resettable.password_reset_action_name) end)
|
||||
|> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec render(Socket.assigns()) :: Rendered.t() | no_return
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@overrides, :root_class)}>
|
||||
<%= if @label do %>
|
||||
<h2 class={override_for(@overrides, :label_class)}><%= @label %></h2>
|
||||
<% end %>
|
||||
|
||||
<.form
|
||||
:let={form}
|
||||
for={@form}
|
||||
phx-change="change"
|
||||
phx-submit="submit"
|
||||
phx-trigger-action={@trigger_action}
|
||||
phx-target={@myself}
|
||||
action={
|
||||
route_helpers(@socket).auth_path(@socket.endpoint, {@subject_name, @strategy.name, :reset})
|
||||
}
|
||||
method="POST"
|
||||
class={override_for(@overrides, :form_class)}
|
||||
>
|
||||
<%= hidden_input(form, :reset_token, value: @token) %>
|
||||
<Input.error socket={@socket} field={:reset_token} form={@form} overrides={@overrides} />
|
||||
|
||||
<Input.password_field
|
||||
socket={@socket}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
overrides={@overrides}
|
||||
/>
|
||||
|
||||
<%= if @strategy.confirmation_required? do %>
|
||||
<Input.password_confirmation_field
|
||||
socket={@socket}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
overrides={@overrides}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<div class={override_for(@overrides, :spacer_class)}></div>
|
||||
|
||||
<Input.submit
|
||||
socket={@socket}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
action={:reset}
|
||||
disable_text={override_for(@overrides, :disable_button_text)}
|
||||
label={humanize(@resettable.password_reset_action_name)}
|
||||
overrides={@overrides}
|
||||
/>
|
||||
</.form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec handle_event(String.t(), %{required(String.t()) => String.t()}, Socket.t()) ::
|
||||
{:noreply, Socket.t()}
|
||||
|
||||
def handle_event("change", params, socket) do
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form =
|
||||
socket.assigns.form
|
||||
|> Form.validate(params, errors: false)
|
||||
|
||||
{:noreply, assign(socket, form: form)}
|
||||
end
|
||||
|
||||
def handle_event("submit", params, socket) do
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form = Form.validate(socket.assigns.form, params)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:form, form)
|
||||
|> assign(:trigger_action, form.valid?)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp get_params(params, strategy) do
|
||||
param_key =
|
||||
strategy.resource
|
||||
|> Info.authentication_subject_name!()
|
||||
|> to_string()
|
||||
|> slugify()
|
||||
|
||||
Map.get(params, param_key, %{})
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ defmodule AshAuthentication.Phoenix.Overrides do
|
|||
generic looking user interface.
|
||||
|
||||
You can override this by adding your own override modules to the
|
||||
`AshAuthentication.Phoenix.Router.sign_in_route/3` macro in your router:
|
||||
`AshAuthentication.Phoenix.Router.sign_in_route/1` macro in your router:
|
||||
|
||||
```elixir
|
||||
sign_in_route overrides: [MyAppWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
|
||||
|
|
|
@ -6,19 +6,40 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
"""
|
||||
|
||||
use AshAuthentication.Phoenix.Overrides
|
||||
alias AshAuthentication.Phoenix.{Components, SignInLive}
|
||||
alias AshAuthentication.Phoenix.{Components, ResetLive, SignInLive}
|
||||
|
||||
override SignInLive do
|
||||
set :root_class, "grid h-screen place-items-center"
|
||||
end
|
||||
|
||||
override ResetLive do
|
||||
set :root_class, "grid h-screen place-items-center"
|
||||
end
|
||||
|
||||
override Components.Reset do
|
||||
set :root_class, """
|
||||
flex-1 flex flex-col justify-center py-12 px-4 sm:px-6 lg:flex-none
|
||||
lg:px-20 xl:px-24
|
||||
"""
|
||||
|
||||
set :strategy_class, "mx-auth w-full max-w-sm lg:w-96"
|
||||
end
|
||||
|
||||
override Components.Reset.Form do
|
||||
set :root_class, nil
|
||||
set :label_class, "mt-2 mb-4 text-2xl tracking-tight font-bold text-gray-900"
|
||||
set :form_class, nil
|
||||
set :spacer_class, "py-1"
|
||||
set :disable_button_text, "Changing password ..."
|
||||
end
|
||||
|
||||
override Components.SignIn do
|
||||
set :root_class, """
|
||||
flex-1 flex flex-col justify-center py-12 px-4 sm:px-6 lg:flex-none
|
||||
lg:px-20 xl:px-24
|
||||
"""
|
||||
|
||||
set :provider_class, "mx-auth w-full max-w-sm lg:w-96"
|
||||
set :strategy_class, "mx-auth w-full max-w-sm lg:w-96"
|
||||
end
|
||||
|
||||
override Components.Banner do
|
||||
|
@ -98,7 +119,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
w-full flex justify-center py-2 px-4 border border-transparent rounded-md
|
||||
shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-600
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
||||
mt-2 mb-4
|
||||
mt-4 mb-4
|
||||
"""
|
||||
|
||||
set :error_ul, "text-red-400 font-light my-3 italic text-sm"
|
||||
|
|
59
lib/ash_authentication_phoenix/reset_live.ex
Normal file
59
lib/ash_authentication_phoenix/reset_live.ex
Normal file
|
@ -0,0 +1,59 @@
|
|||
defmodule AshAuthentication.Phoenix.ResetLive do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
reset_id: "Element ID for the `Reset` LiveComponent."
|
||||
|
||||
@moduledoc """
|
||||
A generic, white-label password reset page.
|
||||
|
||||
This live-view can be rendered into your app using the
|
||||
`AshAuthentication.Phoenix.Router.reset_route/1` macro in your router (or by
|
||||
using `Phoenix.LiveView.Controller.live_render/3` directly in your markup).
|
||||
|
||||
This live-view looks for the `token` URL parameter, and if found passes it to
|
||||
`AshAuthentication.Phoenix.Components.Reset`.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveView
|
||||
alias AshAuthentication.Phoenix.Components
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
def mount(_params, session, socket) do
|
||||
overrides =
|
||||
session
|
||||
|> Map.get("overrides", [AshAuthentication.Phoenix.Overrides.Default])
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(overrides: overrides)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec handle_params(map, String.t(), Socket.t()) :: {:noreply, Socket.t()}
|
||||
def handle_params(%{"token" => token}, _uri, socket) do
|
||||
{:noreply, assign(socket, :token, token)}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec render(Socket.assigns()) :: Rendered.t()
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@overrides, :root_class)}>
|
||||
<.live_component
|
||||
module={Components.Reset}
|
||||
id={override_for(@overrides, :reset_id, "reset")}
|
||||
token={@token}
|
||||
overrides={@overrides}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -29,6 +29,7 @@ defmodule AshAuthentication.Phoenix.Router do
|
|||
sign_in_route
|
||||
sign_out_route AuthController
|
||||
auth_routes_for MyApp.Accounts.User, to: AuthController
|
||||
reset_route
|
||||
end
|
||||
```
|
||||
"""
|
||||
|
@ -174,4 +175,56 @@ defmodule AshAuthentication.Phoenix.Router do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a generic, white-label password reset page using LiveView and the
|
||||
components in `AshAuthentication.Phoenix.Components`.
|
||||
|
||||
Available options are:
|
||||
|
||||
* `path` the path under which to mount the live-view. Defaults to
|
||||
`"/password-reset"`.
|
||||
* `live_view` the name of the live view to render. Defaults to
|
||||
`AshAuthentication.Phoenix.ResetLive`.
|
||||
* `as` which is passed to the generated `live` route. Defaults to `:auth`.
|
||||
* `overrides` specify any override modules for customisation. See
|
||||
`AshAuthentication.Phoenix.Overrides` for more information. all other
|
||||
options are passed to the generated `scope`.
|
||||
|
||||
This is completely optional.
|
||||
"""
|
||||
@spec reset_route(
|
||||
opts :: [
|
||||
{:path, String.t()}
|
||||
| {:live_view, module}
|
||||
| {:as, atom}
|
||||
| {:overrides, [module]}
|
||||
| {atom, any}
|
||||
]
|
||||
) :: Macro.t()
|
||||
defmacro reset_route(opts \\ []) do
|
||||
{path, opts} = Keyword.pop(opts, :path, "/password-reset")
|
||||
{live_view, opts} = Keyword.pop(opts, :live_view, AshAuthentication.Phoenix.ResetLive)
|
||||
{as, opts} = Keyword.pop(opts, :as, :auth)
|
||||
|
||||
{overrides, opts} =
|
||||
Keyword.pop(opts, :overrides, [AshAuthentication.Phoenix.Overrides.Default])
|
||||
|
||||
opts =
|
||||
opts
|
||||
|> Keyword.put_new(:alias, false)
|
||||
|
||||
quote do
|
||||
scope unquote(path), unquote(opts) do
|
||||
import Phoenix.LiveView.Router, only: [live: 4, live_session: 2]
|
||||
|
||||
live_session :reset do
|
||||
live("/:token", unquote(live_view), :reset,
|
||||
as: unquote(as),
|
||||
private: %{overrides: unquote(overrides)}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,8 @@ defmodule AshAuthentication.Phoenix.SignInLive do
|
|||
@moduledoc """
|
||||
A generic, white-label sign-in page.
|
||||
|
||||
This live-view can be rendered into your app by using the
|
||||
`AshAuthentication.Phoenix.Router.sign_in_route/3` macro in your router (or by
|
||||
This live-view can be rendered into your app using the
|
||||
`AshAuthentication.Phoenix.Router.sign_in_route/1` macro in your router (or by
|
||||
using `Phoenix.LiveView.Controller.live_render/3` directly in your markup).
|
||||
|
||||
This live-view finds all Ash resources with an authentication configuration
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -89,7 +89,7 @@ defmodule AshAuthentication.Phoenix.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash_authentication, "~> 3.0"},
|
||||
{:ash_authentication, "~> 3.1"},
|
||||
{:ash_phoenix, "~> 1.1"},
|
||||
{:ash, "~> 2.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,6 +1,6 @@
|
|||
%{
|
||||
"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_authentication": {:hex, :ash_authentication, "3.0.3", "a00a19a2c60a115d530e27ab5a2d768f998069e61d66073254be477394e42361", [:mix], [{:ash, "~> 2.4", [hex: :ash, repo: "hexpm", optional: false]}, {:assent, "~> 0.2", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 0.2.12", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "689e0b3735ab95c6e30c484ae34bf6edb5a66fb37befa4cb7dab77ebd93ddaa8"},
|
||||
"ash_authentication": {:hex, :ash_authentication, "3.1.0", "b6f30a2f1107de113b56e1a29889e385acbccc62cea37dcf2f3a453477396a7a", [:mix], [{:ash, "~> 2.4", [hex: :ash, repo: "hexpm", optional: false]}, {:assent, "~> 0.2", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 0.2.12", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "4cbf27735037cec0d63745a3081d285f71ff873c990871afcea64049b3dc06ff"},
|
||||
"ash_phoenix": {:hex, :ash_phoenix, "1.1.2", "36c46852fa0e739d7217092ac71e3a72e729d5c00758f401234215d8d32f350b", [:mix], [{:ash, "~> 2.0", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "9afb2c9e8dbb68d3549713b4a96b56801c250489efdf4776b931770bd629e0bd"},
|
||||
"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"},
|
||||
|
@ -18,7 +18,7 @@
|
|||
"doctor": {:hex, :doctor, "0.20.0", "2a8ff8f87eaf3fc78f20ffcfa7a3181f2bdb6a115a4abd52582e6156a89649a5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "36ba43bdf7d799c41e1dc00b3429eb48bc5d4dc3f63b181ca1aa8829ec638862"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
|
||||
"ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.7.1", "314f2a5450254db0446ba94cc1ba12a25b83b457f24aa9cc21c128cead5d03aa", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "0f1ad4787b4d7489563351cbf85c9221a852f5441364a2cb3ffd36f2fda7f7fb"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.7.2", "e83548b0500e654d1a595f1134af4862a2e92ec3282ec4c2a17641e9aa45ee73", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "05fb44abf9582381c2eb1b73d485a55288c581071de0ee3ee1084ee69d6a8e5f"},
|
||||
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9e16517a05e48eb7b39d3db190a00a136cb05f8d", []},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
|
||||
|
|
|
@ -87,6 +87,10 @@ defmodule Example.Accounts.User do
|
|||
tokens do
|
||||
enabled?(true)
|
||||
token_resource(Example.Accounts.Token)
|
||||
|
||||
signing_secret(fn _, _ ->
|
||||
Application.fetch_env(:ash_authentication_phoenix, :signing_secret)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue