improvement: optional support for routing to register & reset links (#281)

This commit is contained in:
Zach Daniel 2023-09-25 10:26:49 -04:00 committed by GitHub
parent ca1b5d6f58
commit 50f7147d26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 25 deletions

View file

@ -375,7 +375,9 @@ defmodule ExampleWeb.Router do
get "/", PageController, :home
# add these lines -->
sign_in_route()
# Leave out `register_path` and `reset_path` if you don't want to support
# user registration and/or password resets respectively.
sign_in_route(register_path: "/register", reset_path: "/reset")
sign_out_route AuthController
auth_routes_for Example.Accounts.User, to: AuthController
reset_route []

View file

@ -2,7 +2,8 @@ defmodule AshAuthentication.Phoenix.Components.Password do
use AshAuthentication.Phoenix.Overrides.Overridable,
root_class: "CSS class for the root `div` element.",
hide_class: "CSS class to apply to hide an element.",
show_first: "The form to show on first load. Either `:sign_in` or `:register`.",
show_first:
"The form to show on first load. Either `:sign_in` or `:register`. Only relevant if paths aren't set for them in the router.",
interstitial_class: "CSS class for the `div` element between the form and the button.",
sign_in_toggle_text:
"Toggle text to display when the sign in form is not showing (or `nil` to disable).",
@ -32,8 +33,6 @@ defmodule AshAuthentication.Phoenix.Components.Password do
* `strategy` - The strategy configuration as per
`AshAuthentication.Info.strategy/2`. Required.
* `overrides` - A list of override modules.
* `show_first` - either `:sign_in`, `:register` or `:reset` which controls
which form is visible on first load.
## Slots
@ -43,6 +42,9 @@ defmodule AshAuthentication.Phoenix.Components.Password do
passed as a slot argument.
* `reset_extra` - rendered inside the reset form with the form passed as a
slot argument.
* `path` - used as the base for links to other pages.
* `reset_path` - the path to use for reset links.
* `register_path` - the path to use for register links.
```heex
<.live_component
@ -78,7 +80,9 @@ defmodule AshAuthentication.Phoenix.Components.Password do
@type props :: %{
required(:strategy) => AshAuthentication.Strategy.t(),
optional(:overrides) => [module]
optional(:overrides) => [module],
optional(:live_action) => :sign_in | :register,
optional(:path) => String.t()
}
slot :sign_in_extra
@ -127,17 +131,30 @@ defmodule AshAuthentication.Phoenix.Components.Password do
:register_id,
generate_id(subject_name, strategy_name, strategy.register_action_name)
)
|> assign_new(:show_first, fn -> override_for(assigns.overrides, :show_first, :sign_in) end)
|> assign(:hide_class, override_for(assigns.overrides, :hide_class))
|> assign(:reset_enabled?, reset_enabled?)
|> assign(:register_enabled?, register_enabled?)
|> assign(:sign_in_enabled?, !is_nil(override_for(assigns.overrides, :sign_in_toggle_text)))
|> assign(:reset_id, reset_id)
|> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end)
|> assign_new(:live_action, fn -> :sign_in end)
|> assign_new(:path, fn -> "/" end)
|> assign_new(:reset_path, fn -> nil end)
|> assign_new(:register_path, fn -> nil end)
show =
if assigns[:live_action] == :sign_in && is_nil(assigns[:reset_path]) &&
is_nil(assigns[:register_path]) do
assigns[:show_first] || :sign_in
else
assigns[:live_action]
end
assigns = assign(assigns, :show, show)
~H"""
<div class={override_for(@overrides, :root_class)}>
<div id={"#{@sign_in_id}-wrapper"} class={unless @show_first == :sign_in, do: @hide_class}>
<div id={"#{@sign_in_id}-wrapper"} class={if @show == :sign_in, do: nil, else: @hide_class}>
<.live_component
:let={form}
module={Password.SignInForm}
@ -155,18 +172,20 @@ defmodule AshAuthentication.Phoenix.Components.Password do
<div class={override_for(@overrides, :interstitial_class)}>
<%= if @reset_enabled? do %>
<.toggler
message={override_for(@overrides, :reset_toggle_text)}
show={@reset_id}
hide={[@sign_in_id, @register_id]}
message={override_for(@overrides, :reset_toggle_text)}
to={@reset_path}
overrides={@overrides}
/>
<% end %>
<%= if @register_enabled? do %>
<.toggler
message={override_for(@overrides, :register_toggle_text)}
show={@register_id}
hide={[@sign_in_id, @reset_id]}
message={override_for(@overrides, :register_toggle_text)}
to={@register_path}
overrides={@overrides}
/>
<% end %>
@ -175,7 +194,10 @@ defmodule AshAuthentication.Phoenix.Components.Password do
</div>
<%= if @register_enabled? do %>
<div id={"#{@register_id}-wrapper"} class={unless @show_first == :register, do: @hide_class}>
<div
id={"#{@register_id}-wrapper"}
class={if @live_action == :register, do: nil, else: @hide_class}
>
<.live_component
:let={form}
module={Password.RegisterForm}
@ -193,17 +215,19 @@ defmodule AshAuthentication.Phoenix.Components.Password do
<div class={override_for(@overrides, :interstitial_class)}>
<%= if @reset_enabled? do %>
<.toggler
message={override_for(@overrides, :reset_toggle_text)}
show={@reset_id}
hide={[@sign_in_id, @register_id]}
message={override_for(@overrides, :reset_toggle_text)}
to={@reset_path}
overrides={@overrides}
/>
<% end %>
<%= if @sign_in_enabled? do %>
<.toggler
message={override_for(@overrides, :sign_in_toggle_text)}
show={@sign_in_id}
hide={[@register_id, @reset_id]}
message={override_for(@overrides, :sign_in_toggle_text)}
to={@path}
overrides={@overrides}
/>
<% end %>
@ -213,7 +237,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
<% end %>
<%= if @reset_enabled? do %>
<div id={"#{@reset_id}-wrapper"} class={unless @show_first == :reset, do: @hide_class}>
<div id={"#{@reset_id}-wrapper"} class={if @show == :reset, do: nil, else: @hide_class}>
<.live_component
:let={form}
module={Password.ResetForm}
@ -231,6 +255,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
<div class={override_for(@overrides, :interstitial_class)}>
<%= if @register_enabled? do %>
<.toggler
to={@register_path}
show={@register_id}
hide={[@sign_in_id, @reset_id]}
message={override_for(@overrides, :register_toggle_text)}
@ -239,6 +264,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
<% end %>
<%= if @sign_in_enabled? do %>
<.toggler
to={@path}
show={@sign_in_id}
hide={[@register_id, @reset_id]}
message={override_for(@overrides, :sign_in_toggle_text)}
@ -265,12 +291,20 @@ defmodule AshAuthentication.Phoenix.Components.Password do
@doc false
@spec toggler(Socket.assigns()) :: Rendered.t() | no_return
def toggler(assigns) do
if assigns[:to] do
~H"""
<.link patch={@to} class={override_for(@overrides, :toggler_class)}>
<%= @message %>
</.link>
"""
else
~H"""
<a href="#" phx-click={toggle_js(@show, @hide)} class={override_for(@overrides, :toggler_class)}>
<%= @message %>
</a>
"""
end
end
defp toggle_js(show, hides, %JS{} = js \\ %JS{}) do
show_wrapper = "##{show}-wrapper"

View file

@ -42,7 +42,8 @@ defmodule AshAuthentication.Phoenix.Components.Password.RegisterForm do
@type props :: %{
required(:strategy) => AshAuthentication.Strategy.t(),
optional(:overrides) => [module]
optional(:overrides) => [module],
optional(:live_action) => :sign_in | :register
}
@doc false

View file

@ -33,6 +33,10 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
* `overrides` - A list of override modules.
* `otp_app` - The otp app to look for authenticated resources in
* `live_action` - The live_action being routed to
* `path` - The path to use as the base for links
* `reset_path` - The path to use for reset links
* `register_path` - The path to use for register links
"""
use Phoenix.LiveComponent
@ -42,7 +46,10 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
import Slug
@type props :: %{
optional(:overrides) => [module]
optional(:overrides) => [module],
optional(:path) => String.t(),
optional(:reset_path) => String.t(),
optional(:register_path) => String.t()
}
@doc false
@ -68,6 +75,10 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
socket
|> assign(:strategies_by_resource, strategies_by_resource)
|> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end)
|> assign_new(:live_action, fn -> :sign_in end)
|> assign_new(:path, fn -> "/" end)
|> assign_new(:reset_path, fn -> nil end)
|> assign_new(:register_path, fn -> nil end)
{:ok, socket}
end
@ -87,7 +98,11 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
<%= for strategy <- strategies.form do %>
<.strategy
component={component_for_strategy(strategy)}
live_action={@live_action}
strategy={strategy}
path={@path}
reset_path={@reset_path}
register_path={@register_path}
overrides={@overrides}
/>
<% end %>
@ -105,7 +120,11 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
<%= for strategy <- strategies.link do %>
<.strategy
component={component_for_strategy(strategy)}
live_action={@live_action}
strategy={strategy}
path={@path}
reset_path={@reset_path}
register_path={@register_path}
overrides={@overrides}
/>
<% end %>
@ -122,6 +141,10 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
module={@component}
id={strategy_id(@strategy)}
strategy={@strategy}
path={@path}
reset_path={@reset_path}
register_path={@register_path}
live_action={@live_action}
overrides={@overrides}
/>
</div>

View file

@ -90,6 +90,14 @@ defmodule AshAuthentication.Phoenix.Router do
subject_name = AshAuthentication.Info.authentication_subject_name!(unquote(resource))
controller = Keyword.fetch!(unquote(opts), :to)
path = Keyword.get(unquote(opts), :path, "/auth")
path =
if String.starts_with?(path, "/") do
path
else
"/" <> path
end
scope_opts = Keyword.get(unquote(opts), :scope_opts, [])
strategies =
@ -119,8 +127,11 @@ defmodule AshAuthentication.Phoenix.Router do
Available options are:
* `path` the path under which to mount the live-view. Defaults to
`"/sign-in"`.
* `path` the path under which to mount the sign-in live-view. Defaults to `"/sign-in"`.
* `register_path` - the path under which to mount the password strategy's registration live-view.
If not set, and registration is supported, registration will use a dynamic toggle and will not be routeable to.
* `register_path` - the path under which to mount the password strategy's password reset live-view.
If not set, and password reset is supported, password reset will use a dynamic toggle and will not be routeable to.
* `live_view` the name of the live view to render. Defaults to
`AshAuthentication.Phoenix.SignInLive`.
* `as` which is passed to the generated `live` route. Defaults to `:auth`.
@ -147,6 +158,8 @@ defmodule AshAuthentication.Phoenix.Router do
{otp_app, opts} = Keyword.pop(opts, :otp_app)
{layout, opts} = Keyword.pop(opts, :layout)
{on_mount, opts} = Keyword.pop(opts, :on_mount)
{reset_path, opts} = Keyword.pop(opts, :reset_path)
{register_path, opts} = Keyword.pop(opts, :register_path)
{overrides, opts} =
Keyword.pop(opts, :overrides, [AshAuthentication.Phoenix.Overrides.Default])
@ -156,11 +169,17 @@ defmodule AshAuthentication.Phoenix.Router do
|> Keyword.put_new(:alias, false)
quote do
scope unquote(path), unquote(opts) do
scope "/", unquote(opts) do
import Phoenix.LiveView.Router, only: [live: 4, live_session: 3]
live_session_opts = [
session: %{"overrides" => unquote(overrides), "otp_app" => unquote(otp_app)},
session: %{
"overrides" => unquote(overrides),
"otp_app" => unquote(otp_app),
"path" => unquote(path),
"reset_path" => unquote(reset_path),
"register_path" => unquote(register_path)
},
on_mount: [AshAuthenticationPhoenix.Router.OnLiveViewMount | unquote(on_mount || [])]
]
@ -174,7 +193,17 @@ defmodule AshAuthentication.Phoenix.Router do
end
live_session :sign_in, live_session_opts do
live("/", unquote(live_view), :sign_in, as: unquote(as))
live(unquote(path), unquote(live_view), :sign_in, as: unquote(as))
if unquote(reset_path) do
live(unquote(reset_path), unquote(live_view), :reset, as: :"#{unquote(as)}_reset")
end
if unquote(register_path) do
live(unquote(register_path), unquote(live_view), :register,
as: :"#{unquote(as)}_register"
)
end
end
end
end

View file

@ -33,10 +33,18 @@ defmodule AshAuthentication.Phoenix.SignInLive do
socket
|> assign(overrides: overrides)
|> assign_new(:otp_app, fn -> nil end)
|> assign(:path, session["path"] || "/")
|> assign(:reset_path, session["reset_path"])
|> assign(:register_path, session["register_path"])
{:ok, socket}
end
@impl true
def handle_params(_, _uri, socket) do
{:noreply, socket}
end
@doc false
@impl true
@spec render(Socket.assigns()) :: Rendered.t()
@ -46,6 +54,10 @@ defmodule AshAuthentication.Phoenix.SignInLive do
<.live_component
module={Components.SignIn}
otp_app={@otp_app}
live_action={@live_action}
path={@path}
reset_path={@reset_path}
register_path={@register_path}
id={override_for(@overrides, :sign_in_id, "sign-in")}
overrides={@overrides}
/>