mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-19 21:13:52 +12:00
improvement!: Update to support AshAuthentication 3.x (#27)
This commit is contained in:
parent
d7c097f10d
commit
1d3e4e4641
26 changed files with 552 additions and 586 deletions
|
@ -27,9 +27,13 @@ config :ash_authentication, AshAuthentication.Jwt,
|
|||
config :phoenix, :json_library, Jason
|
||||
|
||||
config :ash_authentication_phoenix, Example.Accounts.User,
|
||||
oauth2_authentication: [
|
||||
client_id: System.get_env("OAUTH2_CLIENT_ID"),
|
||||
client_secret: System.get_env("OAUTH2_CLIENT_SECRET"),
|
||||
redirect_uri: "http://localhost:4000/auth",
|
||||
site: System.get_env("OAUTH2_SITE")
|
||||
authentication: [
|
||||
strategies: [
|
||||
auth0: [
|
||||
client_id: System.get_env("OAUTH2_CLIENT_ID"),
|
||||
client_secret: System.get_env("OAUTH2_CLIENT_SECRET"),
|
||||
redirect_uri: "http://localhost:4000/auth",
|
||||
site: System.get_env("OAUTH2_SITE")
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
defmodule Dev.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
@ -8,22 +6,15 @@ defmodule Dev.Application do
|
|||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
# Start the PubSub system
|
||||
{Phoenix.PubSub, name: Dev.PubSub},
|
||||
# Start the Endpoint (http/https)
|
||||
DevWeb.Endpoint
|
||||
# Start a worker by calling: Dev.Worker.start_link(arg)
|
||||
# {Dev.Worker, arg}
|
||||
DevWeb.Endpoint,
|
||||
{AshAuthentication.Supervisor, otp_app: :ash_authentication_phoenix}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Dev.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
# whenever the application is updated.
|
||||
@impl true
|
||||
def config_change(changed, _new, removed) do
|
||||
DevWeb.Endpoint.config_change(changed, removed)
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule DevWeb.AuthController do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
def success(conn, user, _token) do
|
||||
def success(conn, _activity, user, _token) do
|
||||
conn
|
||||
|> store_in_session(user)
|
||||
|> assign(:current_user, user)
|
||||
|
@ -16,7 +16,7 @@ defmodule DevWeb.AuthController do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
def failure(conn, reason) do
|
||||
def failure(conn, _activity, reason) do
|
||||
conn
|
||||
|> assign(:failure_reason, reason)
|
||||
|> put_status(401)
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule DevWeb.Router do
|
|||
@moduledoc false
|
||||
|
||||
use DevWeb, :router
|
||||
use AshAuthentication.Phoenix.Router
|
||||
use AshAuthentication.Phoenix.Router, otp_app: :ash_authentication_phoenix
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
|
@ -31,7 +31,7 @@ defmodule DevWeb.Router do
|
|||
|
||||
scope "/", DevWeb do
|
||||
pipe_through :browser
|
||||
auth_routes(AuthController, "/auth")
|
||||
auth_routes_for(Example.Accounts.User, to: AuthController, path: "/auth")
|
||||
sign_in_route("/sign-in")
|
||||
sign_out_route(AuthController, "/sign-out")
|
||||
end
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
defmodule AshAuthentication.Phoenix.AuthenticationComponent do
|
||||
@moduledoc """
|
||||
A basic behaviour to help us lay our components out on the screen.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
defmodule MyComponent do
|
||||
use AshAuthentication.Phoenix.AuthenticationComponent, style: :link
|
||||
end
|
||||
```
|
||||
|
||||
We have two "styles" of component, `:form` and `:link`, which defines how they
|
||||
should be rendered before or after the "or" divider.
|
||||
|
||||
You will need to implement this if you're using
|
||||
`AshAuthentication.Phoenix.Components.SignIn` for your component to be
|
||||
visible. You can ignore if it you're not.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Is the component a `:link` style?
|
||||
|
||||
Auto-generated, but overridable.
|
||||
"""
|
||||
@callback link? :: boolean
|
||||
|
||||
@doc """
|
||||
Is the component a `:form` style?
|
||||
|
||||
Auto-generated, but overridable.
|
||||
"""
|
||||
@callback form? :: boolean
|
||||
|
||||
@type options :: [{:style, :link | :form}]
|
||||
|
||||
@doc false
|
||||
@spec __using__(options) :: Macro.t()
|
||||
defmacro __using__(opts) do
|
||||
style = Keyword.get(opts, :style)
|
||||
|
||||
quote do
|
||||
@behaviour AshAuthentication.Phoenix.AuthenticationComponent
|
||||
|
||||
@doc false
|
||||
@spec link? :: boolean
|
||||
def link?, do: unquote(style == :link)
|
||||
|
||||
@doc false
|
||||
@spec form? :: boolean
|
||||
def form?, do: unquote(style == :form)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,8 @@ defmodule AshAuthentication.Phoenix.Components.Banner do
|
|||
@moduledoc """
|
||||
Renders a very simple banner at the top of the sign-in component.
|
||||
|
||||
Can show either an image or some text, depending on the provided overrides.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.OAuth2Authentication do
|
||||
defmodule AshAuthentication.Phoenix.Components.OAuth2 do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS classes for the root `div` element.",
|
||||
link_class: "CSS classes for the `a` element."
|
||||
|
@ -13,15 +13,15 @@ defmodule AshAuthentication.Phoenix.Components.OAuth2Authentication do
|
|||
|
||||
## Props
|
||||
|
||||
* `provider` - The provider module.
|
||||
* `config` - The configuration as per
|
||||
`AshAuthentication.authenticated_resources/1`. Required.
|
||||
* `strategy` - The strategy configuration as per
|
||||
`AshAuthentication.Info.strategy/2`. Required.
|
||||
* `socket` - Needed to infer the otp-app from the Phoenix endpoint.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
use AshAuthentication.Phoenix.AuthenticationComponent, style: :link
|
||||
alias AshAuthentication.Info
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import AshAuthentication.Phoenix.Components.Helpers, only: [route_helpers: 1]
|
||||
import Phoenix.HTML.Form
|
||||
|
@ -29,30 +29,30 @@ defmodule AshAuthentication.Phoenix.Components.OAuth2Authentication do
|
|||
@doc false
|
||||
@spec render(Socket.assigns()) :: Rendered.t() | no_return
|
||||
def render(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:subject_name, Info.authentication_subject_name!(assigns.strategy.resource))
|
||||
|
||||
~H"""
|
||||
<div class={override_for(@socket, :root_class)}>
|
||||
<a
|
||||
href={
|
||||
route_helpers(@socket).auth_request_path(
|
||||
route_helpers(@socket).auth_path(
|
||||
@socket.endpoint,
|
||||
:request,
|
||||
@config.subject_name,
|
||||
@provider.provides(@config.resource)
|
||||
{@subject_name, @strategy.name, :request}
|
||||
)
|
||||
}
|
||||
class={override_for(@socket, :link_class)}
|
||||
>
|
||||
Sign in with <%= provider_name(@provider, @config) %>
|
||||
Sign in with <%= strategy_name(@strategy) %>
|
||||
</a>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp provider_name(provider, config) do
|
||||
config.resource
|
||||
|> provider.provides()
|
||||
|> case do
|
||||
"oauth2" -> "OAuth"
|
||||
defp strategy_name(strategy) do
|
||||
case strategy.name do
|
||||
:oauth2 -> "OAuth"
|
||||
other -> humanize(other)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
||||
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.",
|
||||
|
@ -10,7 +10,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
toggler_class: "CSS class for the toggler `a` element."
|
||||
|
||||
@moduledoc """
|
||||
Generates sign in and registration forms for a resource.
|
||||
Generates sign in, registration and reset forms for a resource.
|
||||
|
||||
## Component hierarchy
|
||||
|
||||
|
@ -19,67 +19,71 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterForm`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm`
|
||||
* `AshAuthentication.Phoenix.Components.Password.SignInForm`
|
||||
* `AshAuthentication.Phoenix.Components.Password.RegisterForm`
|
||||
* `AshAuthentication.Phoenix.Components.Password.ResetForm`
|
||||
|
||||
## Props
|
||||
|
||||
* `config` - The configuration as per
|
||||
`AshAuthentication.authenticated_resources/1`. Required.
|
||||
* `strategy` - The strategy configuration as per
|
||||
`AshAuthentication.Info.strategy/2`. Required.
|
||||
* `socket` - Needed to infer the otp-app from the Phoenix endpoint.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
use AshAuthentication.Phoenix.AuthenticationComponent, style: :form
|
||||
alias __MODULE__
|
||||
alias AshAuthentication.{PasswordAuthentication.Info, PasswordReset}
|
||||
alias AshAuthentication.{Info, Phoenix.Components.Password}
|
||||
alias Phoenix.LiveView.{JS, Rendered, Socket}
|
||||
import Slug
|
||||
|
||||
@doc false
|
||||
@spec render(Socket.assigns()) :: Rendered.t() | no_return
|
||||
def render(assigns) do
|
||||
config = assigns.config
|
||||
provider = assigns.provider
|
||||
sign_in_action = Info.password_authentication_sign_in_action_name!(assigns.config.resource)
|
||||
register_action = Info.password_authentication_register_action_name!(assigns.config.resource)
|
||||
reset_enabled? = PasswordReset.enabled?(assigns.config.resource)
|
||||
strategy = assigns.strategy
|
||||
|
||||
reset_action =
|
||||
if reset_enabled?,
|
||||
do: PasswordReset.Info.request_password_reset_action_name!(assigns.config.resource)
|
||||
subject_name =
|
||||
assigns.strategy.resource
|
||||
|> Info.authentication_get_by_subject_action_name!()
|
||||
|> to_string()
|
||||
|> slugify()
|
||||
|
||||
strategy_name =
|
||||
assigns.strategy.name
|
||||
|> to_string()
|
||||
|> slugify()
|
||||
|
||||
reset_enabled? = Enum.any?(strategy.resettable)
|
||||
|
||||
reset_id =
|
||||
strategy.resettable
|
||||
|> Enum.map(
|
||||
&generate_id(subject_name, strategy_name, &1.request_password_reset_action_name)
|
||||
)
|
||||
|> List.first()
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:sign_in_action, sign_in_action)
|
||||
|> assign_new(:sign_in_id, fn ->
|
||||
"#{config.subject_name}_#{provider.provides(config.resource)}_#{sign_in_action}"
|
||||
end)
|
||||
|> assign(:register_action, register_action)
|
||||
|> assign_new(:register_id, fn ->
|
||||
"#{config.subject_name}_#{provider.provides(config.resource)}_#{register_action}"
|
||||
end)
|
||||
|> assign_new(:show_first, fn ->
|
||||
override_for(assigns.socket, :show_first, :sign_in)
|
||||
end)
|
||||
|> assign_new(:hide_class, fn ->
|
||||
override_for(assigns.socket, :hide_class)
|
||||
end)
|
||||
|> assign(
|
||||
:sign_in_id,
|
||||
generate_id(subject_name, strategy_name, strategy.sign_in_action_name)
|
||||
)
|
||||
|> assign(
|
||||
:register_id,
|
||||
generate_id(subject_name, strategy_name, strategy.register_action_name)
|
||||
)
|
||||
|> assign(:show_first, override_for(assigns.socket, :show_first, :sign_in))
|
||||
|> assign(:hide_class, override_for(assigns.socket, :hide_class))
|
||||
|> assign(:reset_enabled?, reset_enabled?)
|
||||
|> assign_new(:reset_id, fn ->
|
||||
if reset_enabled?,
|
||||
do: "#{config.subject_name}_#{provider.provides(config.resource)}_#{reset_action}"
|
||||
end)
|
||||
|> assign(:reset_id, reset_id)
|
||||
|
||||
~H"""
|
||||
<div class={override_for(@socket, :root_class)}>
|
||||
<div id={"#{@sign_in_id}-wrapper"} class={unless @show_first == :sign_in, do: @hide_class}>
|
||||
<.live_component
|
||||
module={PasswordAuthentication.SignInForm}
|
||||
module={Password.SignInForm}
|
||||
id={@sign_in_id}
|
||||
provider={@provider}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
label={false}
|
||||
>
|
||||
<div class={override_for(@socket, :interstitial_class)}>
|
||||
|
@ -101,12 +105,12 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
</div>
|
||||
</.live_component>
|
||||
</div>
|
||||
|
||||
<div id={"#{@register_id}-wrapper"} class={unless @show_first == :register, do: @hide_class}>
|
||||
<.live_component
|
||||
module={PasswordAuthentication.RegisterForm}
|
||||
module={Password.RegisterForm}
|
||||
id={@register_id}
|
||||
provider={@provider}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
label={false}
|
||||
>
|
||||
<div class={override_for(@socket, :interstitial_class)}>
|
||||
|
@ -118,7 +122,6 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
message={override_for(@socket, :reset_toggle_text)}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<.toggler
|
||||
socket={@socket}
|
||||
show={@sign_in_id}
|
||||
|
@ -128,13 +131,13 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
</div>
|
||||
</.live_component>
|
||||
</div>
|
||||
|
||||
<%= if @reset_enabled? do %>
|
||||
<div id={"#{@reset_id}-wrapper"} class={unless @show_first == :reset, do: @hide_class}>
|
||||
<.live_component
|
||||
module={PasswordAuthentication.ResetForm}
|
||||
module={Password.ResetForm}
|
||||
id={@reset_id}
|
||||
provider={@provider}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
label={false}
|
||||
>
|
||||
<div class={override_for(@socket, :interstitial_class)}>
|
||||
|
@ -144,7 +147,6 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
hide={[@sign_in_id, @reset_id]}
|
||||
message={override_for(@socket, :register_toggle_text)}
|
||||
/>
|
||||
|
||||
<.toggler
|
||||
socket={@socket}
|
||||
show={@sign_in_id}
|
||||
|
@ -159,6 +161,15 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication do
|
|||
"""
|
||||
end
|
||||
|
||||
defp generate_id(subject_name, strategy_name, action) do
|
||||
action =
|
||||
action
|
||||
|> to_string()
|
||||
|> slugify()
|
||||
|
||||
"#{subject_name}-#{strategy_name}-#{action}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec toggler(Socket.assigns()) :: Rendered.t() | no_return
|
||||
def toggler(assigns) do
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
||||
defmodule AshAuthentication.Phoenix.Components.Password.Input do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
field_class: "CSS class for `div` elements surrounding the fields.",
|
||||
label_class: "CSS class for `label` elements.",
|
||||
|
@ -11,19 +11,21 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
input_debounce: "Number of milliseconds to debounce input by (or `nil` to disable)."
|
||||
|
||||
@moduledoc """
|
||||
Function components for dealing with form input during password authentication.
|
||||
Function components for dealing with form input during password
|
||||
authentication.
|
||||
|
||||
## Component hierarchy
|
||||
|
||||
These function components are consumed by
|
||||
`AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm` and
|
||||
`AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterForm`.
|
||||
`AshAuthentication.Phoenix.Components.Password.SignInForm`,
|
||||
`AshAuthentication.Phoenix.Components.Password.RegisterForm` and
|
||||
`AshAuthentication.Phoenix.Components.ResetForm`.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.Component
|
||||
alias AshAuthentication.{PasswordAuthentication, PasswordReset}
|
||||
alias AshAuthentication.Strategy
|
||||
alias AshPhoenix.Form
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import Phoenix.HTML.Form
|
||||
|
@ -36,7 +38,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
* `socket` - Phoenix LiveView socket.
|
||||
This is needed to be able to retrieve the correct CSS configuration.
|
||||
Required.
|
||||
* `config` - The configuration map as per
|
||||
* `strategy` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
Required.
|
||||
* `form` - An `AshPhoenix.Form`.
|
||||
|
@ -46,13 +48,12 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
"""
|
||||
@spec identity_field(%{
|
||||
required(:socket) => Socket.t(),
|
||||
required(:config) => AshAuthentication.resource_config(),
|
||||
required(:form) => AshPhoenix.Form.t(),
|
||||
required(:strategy) => Strategy.t(),
|
||||
required(:form) => Form.t(),
|
||||
optional(:input_type) => :text | :email
|
||||
}) :: Rendered.t() | no_return
|
||||
def identity_field(assigns) do
|
||||
identity_field =
|
||||
PasswordAuthentication.Info.password_authentication_identity_field!(assigns.config.resource)
|
||||
identity_field = assigns.strategy.identity_field
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|
@ -60,7 +61,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
|> assign_new(:input_type, fn ->
|
||||
identity_field
|
||||
|> to_string()
|
||||
|> String.starts_with?("email")
|
||||
|> String.contains?("email")
|
||||
|> then(fn
|
||||
true -> :email
|
||||
_ -> :text
|
||||
|
@ -95,7 +96,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
* `socket` - Phoenix LiveView socket.
|
||||
This is needed to be able to retrieve the correct CSS configuration.
|
||||
Required.
|
||||
* `config` - The configuration map as per
|
||||
* `strategy` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
Required.
|
||||
* `form` - An `AshPhoenix.Form`.
|
||||
|
@ -103,12 +104,11 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
"""
|
||||
@spec password_field(%{
|
||||
required(:socket) => Socket.t(),
|
||||
required(:config) => AshAuthentication.resource_config(),
|
||||
required(:form) => AshPhoenix.Form.t()
|
||||
required(:strategy) => Strategy.t(),
|
||||
required(:form) => Form.t()
|
||||
}) :: Rendered.t() | no_return
|
||||
def password_field(assigns) do
|
||||
password_field =
|
||||
PasswordAuthentication.Info.password_authentication_password_field!(assigns.config.resource)
|
||||
password_field = assigns.strategy.password_field
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|
@ -142,7 +142,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
* `socket` - Phoenix LiveView socket.
|
||||
This is needed to be able to retrieve the correct CSS configuration.
|
||||
Required.
|
||||
* `config` - The configuration map as per
|
||||
* `strategy` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
Required.
|
||||
* `form` - An `AshPhoenix.Form`.
|
||||
|
@ -150,14 +150,11 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
"""
|
||||
@spec password_confirmation_field(%{
|
||||
required(:socket) => Socket.t(),
|
||||
required(:config) => AshAuthentication.resource_config(),
|
||||
required(:form) => AshPhoenix.Form.t()
|
||||
required(:strategy) => Strategy.t(),
|
||||
required(:form) => Form.t()
|
||||
}) :: Rendered.t() | no_return
|
||||
def password_confirmation_field(assigns) do
|
||||
password_confirmation_field =
|
||||
PasswordAuthentication.Info.password_authentication_password_confirmation_field!(
|
||||
assigns.config.resource
|
||||
)
|
||||
password_confirmation_field = assigns.strategy.password_confirmation_field
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|
@ -191,7 +188,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
* `socket` - Phoenix LiveView socket.
|
||||
This is needed to be able to retrieve the correct CSS configuration.
|
||||
Required.
|
||||
* `config` - The configuration map as per
|
||||
* `strategy` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
Required.
|
||||
* `form` - An `AshPhoenix.Form`.
|
||||
|
@ -204,8 +201,8 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
"""
|
||||
@spec submit(%{
|
||||
required(:socket) => Socket.t(),
|
||||
required(:config) => AshAuthentication.resource_config(),
|
||||
required(:form) => AshPhoenix.Form.t(),
|
||||
required(:strategy) => Strategy.t(),
|
||||
required(:form) => Form.t(),
|
||||
required(:action) => :sign_in | :register,
|
||||
optional(:label) => String.t()
|
||||
}) :: Rendered.t() | no_return
|
||||
|
@ -215,16 +212,15 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.Input do
|
|||
|> assign_new(:label, fn ->
|
||||
case assigns.action do
|
||||
:request_reset ->
|
||||
assigns.config.resource
|
||||
|> PasswordReset.Info.request_password_reset_action_name!()
|
||||
assigns.strategy.resettable
|
||||
|> Enum.map(& &1.request_password_reset_action_name)
|
||||
|> List.first() || :reqest_reset
|
||||
|
||||
:sign_in ->
|
||||
assigns.config.resource
|
||||
|> PasswordAuthentication.Info.password_authentication_sign_in_action_name!()
|
||||
assigns.strategy.sign_in_action_name
|
||||
|
||||
:register ->
|
||||
assigns.config.resource
|
||||
|> PasswordAuthentication.Info.password_authentication_register_action_name!()
|
||||
assigns.strategy.register_action_name
|
||||
end
|
||||
|> humanize()
|
||||
end)
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterForm do
|
||||
defmodule AshAuthentication.Phoenix.Components.Password.RegisterForm do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
label_class: "CSS class for the `h2` element.",
|
||||
|
@ -11,65 +11,61 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
|
||||
## Component hierarchy
|
||||
|
||||
This is a child of `AshAuthentication.Phoenix.Components.PasswordAuthentication`.
|
||||
This is a child of `AshAuthentication.Phoenix.Components.Password`.
|
||||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.password_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.password_confirmation_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.submit/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.identity_field/1`
|
||||
* `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
|
||||
|
||||
* `config` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
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.
|
||||
* `strategy` - The strategy configuration as per
|
||||
`AshAuthentication.Info.strategy/2`. Required.
|
||||
* `socket` - Needed to infer the otp-app from the Phoenix endpoint.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
|
||||
alias AshAuthentication.{
|
||||
PasswordAuthentication,
|
||||
Phoenix.Components.PasswordAuthentication.Input
|
||||
}
|
||||
|
||||
alias AshAuthentication.{Info, Phoenix.Components.Password.Input}
|
||||
alias AshPhoenix.Form
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
|
||||
import Phoenix.HTML.Form
|
||||
import AshAuthentication.Phoenix.Components.Helpers
|
||||
import Slug
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec update(Socket.assigns(), Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
config = assigns.config
|
||||
strategy = assigns.strategy
|
||||
|
||||
action =
|
||||
PasswordAuthentication.Info.password_authentication_register_action_name!(config.resource)
|
||||
|
||||
confirm? =
|
||||
PasswordAuthentication.Info.password_authentication_confirmation_required?(config.resource)
|
||||
api = Info.authentication_api!(strategy.resource)
|
||||
subject_name = Info.authentication_subject_name!(strategy.resource)
|
||||
|
||||
form =
|
||||
config.resource
|
||||
|> Form.for_action(action,
|
||||
api: config.api,
|
||||
as: to_string(config.subject_name),
|
||||
id: "#{PasswordAuthentication.provides(config.resource)}_#{config.subject_name}_#{action}"
|
||||
strategy.resource
|
||||
|> Form.for_action(strategy.register_action_name,
|
||||
api: api,
|
||||
as: subject_name |> to_string(),
|
||||
id: "#{subject_name}-#{strategy.name}-#{strategy.register_action_name}" |> slugify(),
|
||||
context: %{strategy: strategy}
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(form: form, trigger_action: false, confirm?: confirm?)
|
||||
|> assign_new(:label, fn -> humanize(action) end)
|
||||
|> assign(
|
||||
form: form,
|
||||
trigger_action: false,
|
||||
subject_name: subject_name
|
||||
)
|
||||
|> assign_new(:label, fn -> humanize(strategy.register_action_name) end)
|
||||
|> assign_new(:inner_block, fn -> nil end)
|
||||
|
||||
{:ok, socket}
|
||||
|
@ -95,23 +91,19 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
phx-trigger-action={@trigger_action}
|
||||
phx-target={@myself}
|
||||
action={
|
||||
route_helpers(@socket).auth_callback_path(
|
||||
route_helpers(@socket).auth_path(
|
||||
@socket.endpoint,
|
||||
:callback,
|
||||
@config.subject_name,
|
||||
@provider.provides(@config.resource)
|
||||
{@subject_name, @strategy.name, :register}
|
||||
)
|
||||
}
|
||||
method="POST"
|
||||
class={override_for(@socket, :form_class)}
|
||||
>
|
||||
<%= hidden_input(form, :action, value: "register") %>
|
||||
<Input.identity_field socket={@socket} strategy={@strategy} form={form} />
|
||||
<Input.password_field socket={@socket} strategy={@strategy} form={form} />
|
||||
|
||||
<Input.identity_field socket={@socket} config={@config} form={form} />
|
||||
<Input.password_field socket={@socket} config={@config} form={form} />
|
||||
|
||||
<%= if @confirm? do %>
|
||||
<Input.password_confirmation_field socket={@socket} config={@config} form={form} />
|
||||
<%= if @strategy.confirmation_required? do %>
|
||||
<Input.password_confirmation_field socket={@socket} strategy={@strategy} form={form} />
|
||||
<% end %>
|
||||
|
||||
<%= if @inner_block do %>
|
||||
|
@ -122,7 +114,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
|
||||
<Input.submit
|
||||
socket={@socket}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
action={:register}
|
||||
disable_text={override_for(@socket, :disable_button_text)}
|
||||
|
@ -138,8 +130,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
{:noreply, Socket.t()}
|
||||
|
||||
def handle_event("change", params, socket) do
|
||||
config = socket.assigns.config
|
||||
params = Map.get(params, to_string(config.subject_name))
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form =
|
||||
socket.assigns.form
|
||||
|
@ -149,7 +140,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
end
|
||||
|
||||
def handle_event("submit", params, socket) do
|
||||
params = Map.get(params, to_string(socket.assigns.config.subject_name))
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form = Form.validate(socket.assigns.form, params)
|
||||
|
||||
|
@ -160,4 +151,14 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.RegisterFo
|
|||
|
||||
{: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
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm do
|
||||
defmodule AshAuthentication.Phoenix.Components.Password.ResetForm do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
label_class: "CSS class for the `h2` element.",
|
||||
|
@ -13,53 +13,45 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
|
||||
## Component hierarchy
|
||||
|
||||
This is a child of `AshAuthentication.Phoenix.Components.PasswordAuthentication`.
|
||||
This is a child of `AshAuthentication.Phoenix.Components.Password`.
|
||||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.submit/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.submit/1`
|
||||
|
||||
## Props
|
||||
|
||||
* `config` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
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.
|
||||
* `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.{
|
||||
PasswordAuthentication,
|
||||
PasswordReset,
|
||||
Phoenix.Components.PasswordAuthentication.Input
|
||||
}
|
||||
alias AshAuthentication.{Info, Phoenix.Components.Password.Input}
|
||||
|
||||
alias AshPhoenix.Form
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import Phoenix.HTML.Form
|
||||
import AshAuthentication.Phoenix.Components.Helpers
|
||||
import Slug
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec update(Socket.assigns(), Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
config = assigns.config
|
||||
form = blank_form(config)
|
||||
strategy = assigns.strategy
|
||||
form = blank_form(strategy)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(form: form)
|
||||
|> assign_new(:label, fn ->
|
||||
humanize(PasswordReset.Info.request_password_reset_action_name!(config.resource))
|
||||
end)
|
||||
|> assign(form: form, subject_name: Info.authentication_subject_name!(strategy.resource))
|
||||
|> assign_new(:label, fn -> strategy.request_password_reset_action_name end)
|
||||
|> assign_new(:inner_block, fn -> nil end)
|
||||
|
||||
{:ok, socket}
|
||||
|
@ -84,19 +76,15 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
phx-change="change"
|
||||
phx-target={@myself}
|
||||
action={
|
||||
route_helpers(@socket).auth_request_path(
|
||||
route_helpers(@socket).auth_path(
|
||||
@socket.endpoint,
|
||||
:request,
|
||||
@config.subject_name,
|
||||
@provider.provides(@config.resource)
|
||||
{@subject_name, @strategy.name, :reset_request}
|
||||
)
|
||||
}
|
||||
method="POST"
|
||||
class={override_for(@socket, :form_class)}
|
||||
>
|
||||
<%= hidden_input(form, :action, value: "request_password_reset") %>
|
||||
|
||||
<Input.identity_field socket={@socket} config={@config} form={form} />
|
||||
<Input.identity_field socket={@socket} strategy={@strategy} form={form} />
|
||||
|
||||
<%= if @inner_block do %>
|
||||
<div class={override_for(@socket, :slot_class)}>
|
||||
|
@ -106,7 +94,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
|
||||
<Input.submit
|
||||
socket={@socket}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
action={:request_reset}
|
||||
disable_text={override_for(@socket, :disable_button_text)}
|
||||
|
@ -122,8 +110,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
{:noreply, Socket.t()}
|
||||
|
||||
def handle_event("change", params, socket) do
|
||||
config = socket.assigns.config
|
||||
params = Map.get(params, to_string(config.subject_name))
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form =
|
||||
socket.assigns.form
|
||||
|
@ -133,8 +120,8 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
end
|
||||
|
||||
def handle_event("submit", params, socket) do
|
||||
config = socket.assigns.config
|
||||
params = Map.get(params, to_string(config.subject_name))
|
||||
strategy = socket.assigns.strategy
|
||||
params = get_params(params, strategy)
|
||||
|
||||
socket.assigns.form
|
||||
|> Form.validate(params)
|
||||
|
@ -144,7 +131,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:form, blank_form(config))
|
||||
|> assign(:form, blank_form(strategy))
|
||||
|
||||
socket =
|
||||
if flash do
|
||||
|
@ -157,14 +144,28 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.ResetForm
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp blank_form(config) do
|
||||
action = PasswordReset.Info.request_password_reset_action_name!(config.resource)
|
||||
defp get_params(params, strategy) do
|
||||
param_key =
|
||||
strategy.resource
|
||||
|> Info.authentication_subject_name!()
|
||||
|> to_string()
|
||||
|> slugify()
|
||||
|
||||
config.resource
|
||||
|> Form.for_action(action,
|
||||
api: config.api,
|
||||
as: to_string(config.subject_name),
|
||||
id: "#{PasswordAuthentication.provides(config.resource)}_#{config.subject_name}_#{action}"
|
||||
Map.get(params, param_key, %{})
|
||||
end
|
||||
|
||||
defp blank_form(%{resettable: [resettable]} = strategy) do
|
||||
api = Info.authentication_api!(strategy.resource)
|
||||
subject_name = Info.authentication_subject_name!(strategy.resource)
|
||||
|
||||
strategy.resource
|
||||
|> Form.for_action(resettable.request_password_reset_action_name,
|
||||
api: api,
|
||||
as: subject_name |> to_string(),
|
||||
id:
|
||||
"#{subject_name}-#{strategy.name}-#{resettable.request_password_reset_action_name}"
|
||||
|> slugify(),
|
||||
context: %{strategy: strategy}
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm do
|
||||
defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
label_class: "CSS class for the `h2` element.",
|
||||
|
@ -11,41 +11,39 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
|
||||
## Component hierarchy
|
||||
|
||||
This is a child of `AshAuthentication.Phoenix.Components.PasswordAuthentication`.
|
||||
This is a child of `AshAuthentication.Phoenix.Components.Password`.
|
||||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.password_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication.Input.submit/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.identity_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.password_field/1`
|
||||
* `AshAuthentication.Phoenix.Components.Password.Input.submit/1`
|
||||
|
||||
## Props
|
||||
|
||||
* `socket` - Phoenix LiveView socket. This is needed to be able to retrieve
|
||||
the correct CSS configuration.
|
||||
Required.
|
||||
* `config` - The configuration map as per
|
||||
`AshAuthentication.authenticated_resources/1`.
|
||||
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.
|
||||
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.PasswordAuthentication.Info
|
||||
alias AshAuthentication.Phoenix.Components.PasswordAuthentication
|
||||
alias AshAuthentication.Info
|
||||
alias AshAuthentication.Phoenix.Components.Password
|
||||
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(:config) => AshAuthentication.resource_config(),
|
||||
required(:strategy) => AshAuthentication.Strategy.t(),
|
||||
optional(:label) => String.t() | false
|
||||
}
|
||||
|
||||
|
@ -53,23 +51,24 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
@impl true
|
||||
@spec update(props, Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
config = assigns.config
|
||||
action = Info.password_authentication_sign_in_action_name!(config.resource)
|
||||
strategy = assigns.strategy
|
||||
api = Info.authentication_api!(strategy.resource)
|
||||
subject_name = Info.authentication_subject_name!(strategy.resource)
|
||||
|
||||
form =
|
||||
config.resource
|
||||
|> Form.for_action(action,
|
||||
api: config.api,
|
||||
as: to_string(config.subject_name),
|
||||
id:
|
||||
"#{AshAuthentication.PasswordAuthentication.provides(config.resource)}_#{config.subject_name}_#{action}"
|
||||
strategy.resource
|
||||
|> Form.for_action(strategy.sign_in_action_name,
|
||||
api: api,
|
||||
as: subject_name |> to_string(),
|
||||
id: "#{subject_name}-#{strategy.name}-#{strategy.sign_in_action_name}" |> slugify(),
|
||||
context: %{strategy: strategy}
|
||||
)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(form: form, trigger_action: false)
|
||||
|> assign_new(:label, fn -> humanize(action) end)
|
||||
|> assign(form: form, trigger_action: false, subject_name: subject_name)
|
||||
|> assign_new(:label, fn -> humanize(strategy.sign_in_action_name) end)
|
||||
|> assign_new(:inner_block, fn -> nil end)
|
||||
|
||||
{:ok, socket}
|
||||
|
@ -93,20 +92,16 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
phx-trigger-action={@trigger_action}
|
||||
phx-target={@myself}
|
||||
action={
|
||||
route_helpers(@socket).auth_callback_path(
|
||||
route_helpers(@socket).auth_path(
|
||||
@socket.endpoint,
|
||||
:callback,
|
||||
@config.subject_name,
|
||||
@provider.provides(@config.resource)
|
||||
{@subject_name, @strategy.name, :sign_in}
|
||||
)
|
||||
}
|
||||
method="POST"
|
||||
class={override_for(@socket, :form_class)}
|
||||
>
|
||||
<%= hidden_input(form, :action, value: "sign_in") %>
|
||||
|
||||
<PasswordAuthentication.Input.identity_field socket={@socket} config={@config} form={form} />
|
||||
<PasswordAuthentication.Input.password_field socket={@socket} config={@config} form={form} />
|
||||
<Password.Input.identity_field socket={@socket} strategy={@strategy} form={form} />
|
||||
<Password.Input.password_field socket={@socket} strategy={@strategy} form={form} />
|
||||
|
||||
<%= if @inner_block do %>
|
||||
<div class={override_for(@socket, :slot_class)}>
|
||||
|
@ -114,9 +109,9 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<PasswordAuthentication.Input.submit
|
||||
<Password.Input.submit
|
||||
socket={@socket}
|
||||
config={@config}
|
||||
strategy={@strategy}
|
||||
form={form}
|
||||
action={:sign_in}
|
||||
disable_text={override_for(@socket, :disable_button_text)}
|
||||
|
@ -132,8 +127,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
{:noreply, Socket.t()}
|
||||
|
||||
def handle_event("change", params, socket) do
|
||||
config = socket.assigns.config
|
||||
params = Map.get(params, to_string(config.subject_name))
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
|
||||
form =
|
||||
socket.assigns.form
|
||||
|
@ -143,7 +137,7 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
end
|
||||
|
||||
def handle_event("submit", params, socket) do
|
||||
params = Map.get(params, to_string(socket.assigns.config.subject_name))
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
form = Form.validate(socket.assigns.form, params)
|
||||
|
||||
socket =
|
||||
|
@ -153,4 +147,14 @@ defmodule AshAuthentication.Phoenix.Components.PasswordAuthentication.SignInForm
|
|||
|
||||
{: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
|
|
@ -1,17 +1,18 @@
|
|||
defmodule AshAuthentication.Phoenix.Components.SignIn do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
provider_class: "CSS class for a `div` surrounding each provider component."
|
||||
strategy_class: "CSS class for a `div` surrounding each strategy component.",
|
||||
show_banner: "Whether or not to show the banner."
|
||||
|
||||
@moduledoc """
|
||||
Renders sign in mark-up for an authenticated resource.
|
||||
|
||||
This means that it will render sign-in UI for all of the authentication
|
||||
providers for a resource.
|
||||
strategies for a resource.
|
||||
|
||||
For each provider configured on the resource a component name is inferred
|
||||
(e.g. `AshAuthentication.PasswordAuthentication` becomes
|
||||
`AshAuthentication.Phoenix.Components.PasswordAuthentication`) and is rendered
|
||||
For each strategy configured on the resource a component name is inferred
|
||||
(e.g. `AshAuthentication.Strategy.Password` becomes
|
||||
`AshAuthentication.Phoenix.Components.Strategy.Passowrd`) and is rendered
|
||||
into the output.
|
||||
|
||||
## Component hierarchy
|
||||
|
@ -20,36 +21,38 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
|
|||
|
||||
Children:
|
||||
|
||||
* `AshAuthentication.Phoenix.Components.PasswordAuthentication`.
|
||||
|
||||
## Props
|
||||
|
||||
* `config` - The configuration man as per
|
||||
`AshAuthentication.authenticated_resources/1`. Required.
|
||||
* `AshAuthentication.Phoenix.Components.Strategy.Password`.
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
use Phoenix.LiveComponent
|
||||
alias AshAuthentication.Phoenix.Components
|
||||
alias AshAuthentication.{Info, Phoenix.Components, Strategy}
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import AshAuthentication.Phoenix.Components.Helpers
|
||||
import Slug
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec update(Socket.assigns(), Socket.t()) :: {:ok, Socket.t()}
|
||||
def update(assigns, socket) do
|
||||
resources =
|
||||
strategies_by_resource =
|
||||
socket
|
||||
|> otp_app_from_socket()
|
||||
|> AshAuthentication.authenticated_resources()
|
||||
|> Enum.group_by(& &1.subject_name)
|
||||
|> Enum.sort_by(&elem(&1, 0))
|
||||
|> Enum.sort_by(&Info.authentication_subject_name!/1)
|
||||
|> Enum.map(fn resource ->
|
||||
resource
|
||||
|> Info.authentication_strategies()
|
||||
|> Enum.group_by(&strategy_style/1)
|
||||
|> Map.update(:form, [], &sort_strategies_by_name/1)
|
||||
|> Map.update(:link, [], &sort_strategies_by_name/1)
|
||||
end)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:resources, resources)
|
||||
|> assign(:strategies_by_resource, strategies_by_resource)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
@ -60,22 +63,32 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
|
|||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@socket, :root_class)}>
|
||||
<.live_component module={Components.Banner} socket={@socket} id="sign-in-banner" />
|
||||
<%= if override_for(@socket, :show_banner, true) do %>
|
||||
<.live_component module={Components.Banner} socket={@socket} id="sign-in-banner" />
|
||||
<% end %>
|
||||
|
||||
<%= for {_subject_name, configs} <- @resources do %>
|
||||
<%= for config <- configs do %>
|
||||
<%= if has_form?(config) do %>
|
||||
<%= for widget <- form_components(config) do %>
|
||||
<.widget widget={widget} socket={@socket} />
|
||||
<% end %>
|
||||
<%= for strategies <- @strategies_by_resource do %>
|
||||
<%= if Enum.any?(strategies.form) do %>
|
||||
<%= for strategy <- strategies.form do %>
|
||||
<.strategy
|
||||
component={component_for_strategy(strategy)}
|
||||
strategy={strategy}
|
||||
socket={@socket}
|
||||
/>
|
||||
<% end %>
|
||||
<%= if has_form?(config) && has_links?(config) do %>
|
||||
<.live_component module={Components.HorizontalRule} id="hr" />
|
||||
<% end %>
|
||||
<%= if has_links?(config) do %>
|
||||
<%= for widget <- link_components(config) do %>
|
||||
<.widget widget={widget} socket={@socket} />
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= if Enum.any?(strategies.form) && Enum.any?(strategies.link) do %>
|
||||
<.live_component module={Components.HorizontalRule} id="sign-in-hr" />
|
||||
<% end %>
|
||||
|
||||
<%= if Enum.any?(strategies.link) do %>
|
||||
<%= for strategy <- strategies.link do %>
|
||||
<.strategy
|
||||
component={component_for_strategy(strategy)}
|
||||
strategy={strategy}
|
||||
socket={@socket}
|
||||
/>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
@ -83,50 +96,33 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
|
|||
"""
|
||||
end
|
||||
|
||||
defp widget(assigns) do
|
||||
defp strategy(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@socket, :provider_class)}>
|
||||
<.live_component
|
||||
module={@widget.component}
|
||||
id={provider_id(@widget.provider, @widget.config)}
|
||||
provider={@widget.provider}
|
||||
config={@widget.config}
|
||||
/>
|
||||
<div class={override_for(@socket, :strategy_class)}>
|
||||
<.live_component module={@component} id={strategy_id(@strategy)} strategy={@strategy} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp provider_id(provider, config) do
|
||||
"sign-in-#{config.subject_name}-with-#{provider.provides(config.resource)}"
|
||||
defp sort_strategies_by_name(strategies), do: Enum.sort_by(strategies, & &1.name)
|
||||
|
||||
defp strategy_id(strategy) do
|
||||
subject_name =
|
||||
strategy.resource
|
||||
|> Info.authentication_subject_name!()
|
||||
|
||||
"sign-in-#{subject_name}-with-#{strategy.name}"
|
||||
|> slugify()
|
||||
end
|
||||
|
||||
defp has_form?(config), do: config |> form_components() |> Enum.any?()
|
||||
defp has_links?(config), do: config |> link_components() |> Enum.any?()
|
||||
defp strategy_style(%Strategy.Password{}), do: :form
|
||||
defp strategy_style(_), do: :link
|
||||
|
||||
defp form_components(config) do
|
||||
config
|
||||
|> provider_components()
|
||||
|> Enum.filter(& &1.component.form?())
|
||||
end
|
||||
|
||||
defp link_components(config) do
|
||||
config
|
||||
|> provider_components()
|
||||
|> Enum.filter(& &1.component.link?())
|
||||
end
|
||||
|
||||
defp provider_components(%{providers: providers, resource: resource} = config) do
|
||||
providers
|
||||
|> Enum.sort_by(& &1.provides(resource))
|
||||
|> Enum.map(fn provider ->
|
||||
component =
|
||||
provider
|
||||
|> Module.split()
|
||||
|> List.last()
|
||||
|> then(&Module.concat(Components, &1))
|
||||
|
||||
%{component: component, provider: provider, config: config}
|
||||
end)
|
||||
|> Enum.filter(&Code.ensure_loaded?(&1.component))
|
||||
defp component_for_strategy(strategy) do
|
||||
strategy.__struct__
|
||||
|> Module.split()
|
||||
|> List.replace_at(1, "Components")
|
||||
|> List.insert_at(1, "Phoenix")
|
||||
|> Module.concat()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,20 +15,20 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
use MyAppWeb, :controller
|
||||
use AshAuthentication.Phoenix.Controller
|
||||
|
||||
def success(conn, user, _token) do
|
||||
def success(conn, _activity, user, _token) do
|
||||
conn
|
||||
|> store_in_session(user)
|
||||
|> assign(:current_user, user)
|
||||
|> redirect(to: Routes.page_path(conn, :index))
|
||||
end
|
||||
|
||||
def failure(conn, _reason) do
|
||||
def failure(conn, _activity, _reason) do
|
||||
conn
|
||||
|> put_status(401)
|
||||
|> render("failure.html")
|
||||
end
|
||||
|
||||
def sign_out(conn) do
|
||||
def sign_out(conn, _params) do
|
||||
conn
|
||||
|> clear_session()
|
||||
|> render("sign_out.html")
|
||||
|
@ -44,7 +44,7 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
use AshAuthentication.Phoenix.Controller
|
||||
alias AshAuthentication.TokenRevocation
|
||||
|
||||
def success(conn, _user, token) do
|
||||
def success(conn, _activity, _user, token) do
|
||||
conn
|
||||
|> put_status(200)
|
||||
|> json(%{
|
||||
|
@ -54,7 +54,7 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
})
|
||||
end
|
||||
|
||||
def failure(conn, _reason) do
|
||||
def failure(conn, _activity, _reason) do
|
||||
conn
|
||||
|> put_status(401)
|
||||
|> json(%{
|
||||
|
@ -64,7 +64,7 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
})
|
||||
end
|
||||
|
||||
def sign_out(conn) do
|
||||
def sign_out(conn, _params) do
|
||||
conn
|
||||
|> revoke_bearer_tokens()
|
||||
|> json(%{
|
||||
|
@ -76,72 +76,50 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
|
||||
"""
|
||||
|
||||
@typedoc false
|
||||
@type routes :: %{
|
||||
required({String.t(), String.t()}) => %{
|
||||
required(:provider) => module,
|
||||
optional(atom) => any
|
||||
}
|
||||
}
|
||||
|
||||
alias AshAuthentication.Plug.Dispatcher
|
||||
alias Plug.Conn
|
||||
@doc false
|
||||
@callback request(Conn.t(), %{required(String.t()) => String.t()}) :: Conn.t()
|
||||
|
||||
@doc false
|
||||
@callback callback(Conn.t(), %{required(String.t()) => String.t()}) :: Conn.t()
|
||||
@type t :: module
|
||||
|
||||
@type activity :: {strategy_name :: atom, phase :: atom}
|
||||
@type user :: Ash.Resource.record() | nil
|
||||
@type token :: String.t() | nil
|
||||
|
||||
@doc """
|
||||
Called when authentication (or registration, depending on the provider) has been successful.
|
||||
"""
|
||||
@callback success(Conn.t(), user :: Ash.Resource.record(), token :: String.t()) :: Conn.t()
|
||||
@callback success(Conn.t(), activity, user, token) :: Conn.t()
|
||||
|
||||
@doc """
|
||||
Called when authentication fails.
|
||||
"""
|
||||
@callback failure(Conn.t(), nil | Ash.Changeset.t() | Ash.Error.t()) :: Conn.t()
|
||||
@callback failure(Conn.t(), activity, reason :: any) :: Conn.t()
|
||||
|
||||
@doc """
|
||||
Called when a request to sign out is received.
|
||||
"""
|
||||
@callback sign_out(Conn.t(), map) :: Conn.t()
|
||||
@callback sign_out(Conn.t(), params :: map) :: Conn.t()
|
||||
|
||||
@doc false
|
||||
@spec __using__(any) :: Macro.t()
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
@behaviour AshAuthentication.Phoenix.Controller
|
||||
@behaviour AshAuthentication.Plug
|
||||
import Phoenix.Controller
|
||||
import Plug.Conn
|
||||
import AshAuthentication.Phoenix.Plug
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec request(Conn.t(), map) :: Conn.t()
|
||||
def request(conn, params),
|
||||
do:
|
||||
AshAuthentication.Phoenix.Controller.request(
|
||||
conn,
|
||||
params,
|
||||
__MODULE__
|
||||
)
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec callback(Conn.t(), map) :: Conn.t()
|
||||
def callback(conn, params),
|
||||
do:
|
||||
AshAuthentication.Phoenix.Controller.callback(
|
||||
conn,
|
||||
params,
|
||||
__MODULE__
|
||||
)
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec success(Conn.t(), Ash.Resource.record(), nil | AshAuthentication.Jwt.token()) ::
|
||||
@spec success(
|
||||
Conn.t(),
|
||||
AshAuthentication.Phoenix.Controller.activity(),
|
||||
AshAuthentication.Phoenix.Controller.user(),
|
||||
AshAuthentication.Phoenix.Controller.token()
|
||||
) ::
|
||||
Conn.t()
|
||||
def success(conn, user, _token) do
|
||||
def success(conn, _activity, user, _token) do
|
||||
conn
|
||||
|> store_in_session(user)
|
||||
|> put_status(200)
|
||||
|
@ -150,8 +128,9 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec failure(Conn.t(), nil | Ash.Changeset.t() | Ash.Error.t()) :: Conn.t()
|
||||
def failure(conn, _) do
|
||||
@spec failure(Conn.t(), AshAuthentication.Phoenix.Controller.activity(), reason :: any) ::
|
||||
Conn.t()
|
||||
def failure(conn, _activity, _reason) do
|
||||
conn
|
||||
|> put_status(401)
|
||||
|> render("failure.html")
|
||||
|
@ -166,95 +145,64 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
|> render("sign_out.html")
|
||||
end
|
||||
|
||||
defoverridable success: 3, failure: 2, sign_out: 2
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec request(Conn.t(), %{required(String.t()) => String.t()}, module) :: Conn.t()
|
||||
def request(conn, params, return_to) do
|
||||
handle(conn, params, :request, return_to)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec callback(Conn.t(), %{required(String.t()) => String.t()}, module) :: Conn.t()
|
||||
def callback(conn, params, return_to) do
|
||||
handle(conn, params, :callback, return_to)
|
||||
end
|
||||
|
||||
defp handle(conn, _params, phase, return_to) do
|
||||
conn
|
||||
|> generate_routes()
|
||||
|> dispatch(phase, conn)
|
||||
|> return(return_to)
|
||||
end
|
||||
|
||||
defp dispatch(
|
||||
routes,
|
||||
phase,
|
||||
%{params: %{"subject_name" => subject_name, "provider" => provider}} = conn
|
||||
) do
|
||||
case Map.get(routes, {subject_name, provider}) do
|
||||
config when is_map(config) ->
|
||||
conn = Conn.put_private(conn, :authenticator, config)
|
||||
|
||||
case phase do
|
||||
:request -> config.provider.request_plug(conn, [])
|
||||
:callback -> config.provider.callback_plug(conn, [])
|
||||
end
|
||||
|
||||
_ ->
|
||||
@doc false
|
||||
@impl true
|
||||
@spec call(Conn.t(), any) :: Conn.t()
|
||||
def call(%{private: %{strategy: strategy}} = conn, {_subject_name, _stategy_name, phase}) do
|
||||
conn
|
||||
|> Dispatcher.call({phase, strategy, __MODULE__})
|
||||
end
|
||||
|
||||
def call(conn, opts) do
|
||||
super(conn, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec handle_success(
|
||||
Conn.t(),
|
||||
AshAuthentication.Phoenix.Controller.activity(),
|
||||
AshAuthentication.Phoenix.Controller.user(),
|
||||
AshAuthentication.Phoenix.Controller.token()
|
||||
) :: Conn.t()
|
||||
def handle_success(conn, activity, user, token) do
|
||||
conn
|
||||
|> put_private(:phoenix_action, :success)
|
||||
|> put_private(:success_args, [activity, user, token])
|
||||
|> call(:success)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec handle_failure(Conn.t(), AshAuthentication.Phoenix.Controller.activity(), any) ::
|
||||
Conn.t()
|
||||
def handle_failure(conn, activity, reason) do
|
||||
conn
|
||||
|> put_private(:phoenix_action, :failure)
|
||||
|> put_private(:failure_args, [activity, reason])
|
||||
|> call(:failure)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec action(Conn.t(), any) :: Conn.t()
|
||||
def action(conn, opts) do
|
||||
conn
|
||||
|> action_name()
|
||||
|> case do
|
||||
:success ->
|
||||
args = Map.get(conn.private, :success_args, [nil, nil, nil])
|
||||
apply(__MODULE__, :success, [conn | args])
|
||||
|
||||
:failure ->
|
||||
args = Map.get(conn.private, :failure_args, [nil, nil])
|
||||
apply(__MODULE__, :failure, [conn | args])
|
||||
|
||||
_ ->
|
||||
super(conn, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defoverridable success: 4, failure: 3, sign_out: 2
|
||||
end
|
||||
end
|
||||
|
||||
defp dispatch(_routes, _phase, conn), do: conn
|
||||
|
||||
defp return(
|
||||
%{
|
||||
private: %{
|
||||
authentication_result: {:success, user},
|
||||
authenticator: %{resource: resource}
|
||||
}
|
||||
} = conn,
|
||||
return_to
|
||||
)
|
||||
when is_struct(user, resource),
|
||||
do: return_to.success(conn, user, Map.get(user.__metadata__, :token))
|
||||
|
||||
defp return(%{private: %{authentication_result: {:success, nil}}} = conn, return_to),
|
||||
do: return_to.success(conn, nil, nil)
|
||||
|
||||
defp return(%{private: %{authentication_result: {:failure, reason}}} = conn, return_to),
|
||||
do: return_to.failure(conn, reason)
|
||||
|
||||
defp return(conn, return_to), do: return_to.failure(conn, nil)
|
||||
|
||||
# Doing this on every request is probably a really bad idea, but if I do it at
|
||||
# compile time I need to ask for the OTP app all over the place and it reduces
|
||||
# the developer experience sharply.
|
||||
#
|
||||
# Maybe we should just shove them in ETS?
|
||||
defp generate_routes(conn) do
|
||||
:otp_app
|
||||
|> conn.private.phoenix_endpoint.config()
|
||||
|> AshAuthentication.authenticated_resources()
|
||||
|> Stream.flat_map(fn config ->
|
||||
subject_name =
|
||||
config.subject_name
|
||||
|> to_string()
|
||||
|
||||
config
|
||||
|> Map.get(:providers, [])
|
||||
|> Stream.map(fn provider ->
|
||||
config =
|
||||
config
|
||||
|> Map.delete(:providers)
|
||||
|> Map.put(:provider, provider)
|
||||
|
||||
{{subject_name, provider.provides(config.resource)}, config}
|
||||
end)
|
||||
end)
|
||||
|> Map.new()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,25 +9,30 @@ defmodule AshAuthentication.Phoenix.Overrides do
|
|||
You can override by setting the following in your `config.exs`:
|
||||
|
||||
```elixir
|
||||
config :my_app, AshAuthentication.Phoenix, overrides: [MyAppWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
|
||||
config :my_app, AshAuthentication.Phoenix,
|
||||
overrides: [MyAppWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
|
||||
```
|
||||
|
||||
and defining `lib/my_app_web/auth_styles.ex` within which you can set CSS
|
||||
classes for any values you want.
|
||||
and defining `lib/my_app_web/auth_overrides.ex` within which you can set any
|
||||
overrides.
|
||||
|
||||
The `use` macro defines overridable versions of all callbacks which return
|
||||
`nil`, so you only need to define the functions that you care about.
|
||||
|
||||
Each of the override modules specified in the config will be called in the
|
||||
order that they're specified, so you can still use the defaults if you just
|
||||
override some properties.
|
||||
|
||||
```elixir
|
||||
defmodule MyAppWeb.AuthOverrides do
|
||||
use AshAuthentication.Phoenix.Overrides
|
||||
alias AshAuthentication.Phoenix.Components
|
||||
|
||||
override
|
||||
override Components.Banner do
|
||||
set :image_url, "/images/sign_in_logo.png"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
|
@ -45,6 +50,8 @@ defmodule AshAuthentication.Phoenix.Overrides do
|
|||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec __using__(any) :: Macro.t()
|
||||
defmacro __using__(_env) do
|
||||
quote do
|
||||
require AshAuthentication.Phoenix.Overrides
|
||||
|
@ -55,6 +62,10 @@ defmodule AshAuthentication.Phoenix.Overrides do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define overrides for a specific component.
|
||||
"""
|
||||
@spec override(component :: module, do: Macro.t()) :: Macro.t()
|
||||
defmacro override(component, do: block) do
|
||||
quote do
|
||||
@component unquote(component)
|
||||
|
@ -62,12 +73,18 @@ defmodule AshAuthentication.Phoenix.Overrides do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Override a setting within a component.
|
||||
"""
|
||||
@spec set(atom, any) :: Macro.t()
|
||||
defmacro set(selector, value) do
|
||||
quote do
|
||||
@override {@component, unquote(selector), unquote(value)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec __before_compile__(any) :: Macro.t()
|
||||
defmacro __before_compile__(env) do
|
||||
overrides =
|
||||
env.module
|
||||
|
|
|
@ -40,7 +40,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :text, "or"
|
||||
end
|
||||
|
||||
override Components.PasswordAuthentication do
|
||||
override Components.Password do
|
||||
set :root_class, "mt-4 mb-4"
|
||||
set :interstitial_class, "flex flex-row justify-between content-between text-sm font-medium"
|
||||
set :toggler_class, "flex-none text-blue-500 hover:text-blue-600"
|
||||
|
@ -51,7 +51,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :hide_class, "hidden"
|
||||
end
|
||||
|
||||
override Components.PasswordAuthentication.SignInForm do
|
||||
override Components.Password.SignInForm 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
|
||||
|
@ -59,7 +59,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :disable_button_text, "Signing in ..."
|
||||
end
|
||||
|
||||
override Components.PasswordAuthentication.RegisterForm do
|
||||
override Components.Password.RegisterForm 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
|
||||
|
@ -67,7 +67,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :disable_button_text, "Registering ..."
|
||||
end
|
||||
|
||||
override Components.PasswordAuthentication.ResetForm do
|
||||
override Components.Password.ResetForm 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
|
||||
|
@ -79,7 +79,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :disable_button_text, "Requesting ..."
|
||||
end
|
||||
|
||||
override Components.PasswordAuthentication.Input do
|
||||
override Components.Password.Input do
|
||||
set :field_class, "mt-2 mb-2"
|
||||
set :label_class, "block text-sm font-medium text-gray-700 mb-1"
|
||||
|
||||
|
@ -106,7 +106,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :input_debounce, 350
|
||||
end
|
||||
|
||||
override Components.OAuth2Authentication do
|
||||
override Components.OAuth2 do
|
||||
set :root_class, "w-full mt-2 mb-4"
|
||||
|
||||
set :link_class, """
|
||||
|
|
|
@ -42,7 +42,9 @@ defmodule AshAuthentication.Phoenix.Overrides.Overridable do
|
|||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@doc """
|
||||
Retrieve configuration for a potentially overriden value.
|
||||
"""
|
||||
@spec override_for(Socket.t(), atom, any) :: any
|
||||
defmacro override_for(socket, selector, default \\ nil) do
|
||||
overrides =
|
||||
|
|
|
@ -13,7 +13,7 @@ defmodule AshAuthentication.Phoenix.Plug do
|
|||
Attempt to retrieve all actors from the connections' session.
|
||||
|
||||
A wrapper around `AshAuthentication.Plug.Helpers.retrieve_from_session/2`
|
||||
with the `otp_app` already present.
|
||||
with the `otp_app` as extracted from the endpoint.
|
||||
"""
|
||||
@spec load_from_session(Conn.t(), any) :: Conn.t()
|
||||
def load_from_session(conn, _opts) do
|
||||
|
@ -25,7 +25,8 @@ defmodule AshAuthentication.Phoenix.Plug do
|
|||
@doc """
|
||||
Attempt to retrieve actors from the `Authorization` header(s).
|
||||
|
||||
A wrapper around `AshAuthentication.Plug.Helpers.retrieve_from_bearer/2` with the `otp_app` already present.
|
||||
A wrapper around `AshAuthentication.Plug.Helpers.retrieve_from_bearer/2` with
|
||||
the `otp_app` as extracted from the endpoint.
|
||||
"""
|
||||
@spec load_from_bearer(Conn.t(), any) :: Conn.t()
|
||||
def load_from_bearer(conn, _opts) do
|
||||
|
@ -36,7 +37,8 @@ defmodule AshAuthentication.Phoenix.Plug do
|
|||
@doc """
|
||||
Revoke all token(s) in the `Authorization` header(s).
|
||||
|
||||
A wrapper around `AshAuthentication.Plug.Helpers.revoke_bearer_tokens/2` with the `otp_app` already present.
|
||||
A wrapper around `AshAuthentication.Plug.Helpers.revoke_bearer_tokens/2` with
|
||||
the `otp_app` as extracted from the endpoint.
|
||||
"""
|
||||
@spec revoke_bearer_tokens(Conn.t(), any) :: Conn.t()
|
||||
def revoke_bearer_tokens(conn, _opts) do
|
||||
|
|
|
@ -28,16 +28,28 @@ defmodule AshAuthentication.Phoenix.Router do
|
|||
pipe_through :browser
|
||||
sign_in_route
|
||||
sign_out_route AuthController
|
||||
auth_routes AuthController
|
||||
auth_routes_for MyApp.Accounts.User, to: AuthController
|
||||
end
|
||||
```
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@typedoc "Options that can be passed to `auth_routes_for`."
|
||||
@type auth_route_options :: [path_option | to_option | scope_opts_option]
|
||||
|
||||
@typedoc "A sub-path if required. Defaults to `/auth`."
|
||||
@type path_option :: {:path, String.t()}
|
||||
|
||||
@typedoc "The controller which will handle success and failure."
|
||||
@type to_option :: {:to, AshAuthentication.Phoenix.Controller.t()}
|
||||
|
||||
@typedoc "Any options which should be passed to the generated scope."
|
||||
@type scope_opts_option :: {:scope_opts, keyword}
|
||||
|
||||
@doc false
|
||||
@spec __using__(any) :: Macro.t()
|
||||
defmacro __using__(_opts) do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
import AshAuthentication.Phoenix.Router
|
||||
import AshAuthentication.Phoenix.Plug
|
||||
|
@ -45,23 +57,51 @@ defmodule AshAuthentication.Phoenix.Router do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Generates the routes needed for the various subjects and providers
|
||||
authenticating with AshAuthentication.
|
||||
Generates the routes needed for the various strategies for a given
|
||||
AshAuthentication resource.
|
||||
|
||||
This is required if you wish to use authentication.
|
||||
|
||||
## Options
|
||||
|
||||
* `to` - a module which implements the
|
||||
`AshAuthentication.Phoenix.Controller` behaviour. This is required.
|
||||
* `path` - a string (starting with "/") wherein to mount the generated
|
||||
routes.
|
||||
* `scope_opts` - any options to pass to the generated scope.
|
||||
|
||||
## Example
|
||||
|
||||
```elixir
|
||||
scope "/", DevWeb do
|
||||
auth_routes_for(MyApp.Accounts.User,
|
||||
to: AuthController,
|
||||
path: "/authentication",
|
||||
scope_opts: [host: "auth.example.com"]
|
||||
)
|
||||
end
|
||||
```
|
||||
"""
|
||||
defmacro auth_routes(auth_controller, path \\ "auth", opts \\ []) do
|
||||
opts =
|
||||
opts
|
||||
|> Keyword.put_new(:as, :auth)
|
||||
@spec auth_routes_for(Ash.Resource.t(), auth_route_options) :: Macro.t()
|
||||
defmacro auth_routes_for(resource, opts) when is_list(opts) do
|
||||
quote location: :keep do
|
||||
subject_name = AshAuthentication.Info.authentication_subject_name!(unquote(resource))
|
||||
controller = Keyword.fetch!(unquote(opts), :to)
|
||||
path = Keyword.get(unquote(opts), :path, "/auth")
|
||||
scope_opts = Keyword.get(unquote(opts), :scope_opts, [])
|
||||
|
||||
quote do
|
||||
scope unquote(path), unquote(opts) do
|
||||
match(:*, "/:subject_name/:provider", unquote(auth_controller), :request, as: :request)
|
||||
strategies =
|
||||
AshAuthentication.Info.authentication_add_ons(unquote(resource)) ++
|
||||
AshAuthentication.Info.authentication_strategies(unquote(resource))
|
||||
|
||||
match(:*, "/:subject_name/:provider/callback", unquote(auth_controller), :callback,
|
||||
as: :callback
|
||||
)
|
||||
scope path, scope_opts do
|
||||
for strategy <- strategies do
|
||||
for {path, phase} <- AshAuthentication.Strategy.routes(strategy) do
|
||||
match :*, path, controller, {subject_name, strategy.name, phase},
|
||||
as: :auth,
|
||||
private: %{strategy: strategy}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule AshAuthentication.Phoenix.SignInLive do
|
||||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element."
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
sign_in_id: "Element ID for the `SignIn` LiveComponent."
|
||||
|
||||
@moduledoc """
|
||||
A generic, white-label sign-in page.
|
||||
|
@ -27,7 +28,7 @@ defmodule AshAuthentication.Phoenix.SignInLive do
|
|||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@socket, :root_class)}>
|
||||
<.live_component module={Components.SignIn} id="sign-in" />
|
||||
<.live_component module={Components.SignIn} id={override_for(@socket, :sign_in_id, "sign-in")} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -87,7 +87,7 @@ defmodule AshAuthentication.Phoenix.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash_authentication, github: "team-alembic/ash_authentication", tag: "v1.0.0"},
|
||||
{:ash_authentication, github: "team-alembic/ash_authentication", tag: "v3.0.3"},
|
||||
{:ash_phoenix, "~> 1.1"},
|
||||
{:ash, "~> 2.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
|
@ -95,6 +95,7 @@ defmodule AshAuthentication.Phoenix.MixProject do
|
|||
{:phoenix_live_view, "~> 0.18"},
|
||||
{:phoenix, "~> 1.6"},
|
||||
{:bcrypt_elixir, "~> 3.0"},
|
||||
{:slugify, "~> 1.3"},
|
||||
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
||||
{:dialyxir, "~> 1.2", only: [:dev, :test], runtime: false},
|
||||
{:doctor, "~> 0.18", only: [:dev, :test]},
|
||||
|
|
9
mix.lock
9
mix.lock
|
@ -1,6 +1,6 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "2.4.11", "a9fd2616b4ade692361140a0501b03533e60ad9a7a67615041b697daf802efd5", [: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]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, 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 and >= 0.2.10", [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", "0da0b4a8da4247cc14e2ba139c6b8175e33416fa962faba80725bdbf12da355e"},
|
||||
"ash_authentication": {:git, "https://github.com/team-alembic/ash_authentication.git", "5471e498d0114e85e5940dab20aadd77050731ee", [tag: "v1.0.0"]},
|
||||
"ash": {:hex, :ash, "2.4.2", "ba579e6654c32b1da49f17938d2f1445066f27e61eedbf0fae431b816b49d1be", [: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]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:spark, ">= 0.2.0", [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", "da8f94a19cf29617526ca2b1a75f6fae804c1db7c825b49982c603f503a615bd"},
|
||||
"ash_authentication": {:git, "https://github.com/team-alembic/ash_authentication.git", "20c7c20b108c6f5d547b4db0d8f0890e7b59f5e7", [tag: "v3.0.3"]},
|
||||
"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"},
|
||||
|
@ -17,7 +17,7 @@
|
|||
"docsh": {:hex, :docsh, "0.7.2", "f893d5317a0e14269dd7fe79cf95fb6b9ba23513da0480ec6e77c73221cae4f2", [:rebar3], [{:providers, "1.8.1", [hex: :providers, repo: "hexpm", optional: false]}], "hexpm", "4e7db461bb07540d2bc3d366b8513f0197712d0495bb85744f367d3815076134"},
|
||||
"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"},
|
||||
"ecto": {:hex, :ecto, "3.9.1", "67173b1687afeb68ce805ee7420b4261649d5e2deed8fe5550df23bab0bc4396", [: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", "c80bb3d736648df790f7f92f81b36c922d9dd3203ca65be4ff01d067f54eb304"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
|
||||
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9e16517a05e48eb7b39d3db190a00a136cb05f8d", []},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
|
@ -52,8 +52,9 @@
|
|||
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
||||
"providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
|
||||
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},
|
||||
"spark": {:hex, :spark, "0.2.12", "03ebab9ed1ecc577c65fd1ae8b88c41d5ba8420b393658616a657d6d0fc2996f", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "22dfba98a9a6ebb5a21d520fa79cf3e67f9f549fff1c6ade55aa6c1d26814463"},
|
||||
"spark": {:hex, :spark, "0.2.17", "90c201fefe02eba9611733454c6b330ec2adc6eb823ce7aa1f6c83e38c05cd62", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a2a9375bb6d06aab960c6990688258d820160e454e3d22e9669ea0eda37a2e07"},
|
||||
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
||||
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
|
|
|
@ -4,5 +4,6 @@ defmodule Example.Accounts.Registry do
|
|||
|
||||
entries do
|
||||
entry Example.Accounts.User
|
||||
entry Example.Accounts.Token
|
||||
end
|
||||
end
|
||||
|
|
10
test/support/accounts/token.ex
Normal file
10
test/support/accounts/token.ex
Normal file
|
@ -0,0 +1,10 @@
|
|||
defmodule Example.Accounts.Token do
|
||||
@moduledoc false
|
||||
use Ash.Resource,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [AshAuthentication.TokenResource]
|
||||
|
||||
token do
|
||||
api Example.Accounts
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
defmodule Example.Accounts.TokenRevocation do
|
||||
@moduledoc false
|
||||
use Ash.Resource,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [AshAuthentication.TokenRevocation]
|
||||
|
||||
revocation do
|
||||
api Example.Accounts
|
||||
end
|
||||
end
|
|
@ -2,13 +2,7 @@ defmodule Example.Accounts.User do
|
|||
@moduledoc false
|
||||
use Ash.Resource,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [
|
||||
AshAuthentication,
|
||||
AshAuthentication.Confirmation,
|
||||
AshAuthentication.PasswordAuthentication,
|
||||
AshAuthentication.PasswordReset,
|
||||
AshAuthentication.OAuth2Authentication
|
||||
]
|
||||
extensions: [AshAuthentication]
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -50,53 +44,60 @@ defmodule Example.Accounts.User do
|
|||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
# wat 2
|
||||
authentication do
|
||||
api(Example.Accounts)
|
||||
end
|
||||
|
||||
confirmation do
|
||||
monitor_fields([:email])
|
||||
add_ons do
|
||||
confirmation :confirm do
|
||||
monitor_fields([:email])
|
||||
|
||||
sender(fn user, token ->
|
||||
Logger.debug("Confirmation request for #{user.email} with token #{inspect(token)}")
|
||||
end)
|
||||
end
|
||||
sender(fn user, token ->
|
||||
Logger.debug("Confirmation request for #{user.email} with token #{inspect(token)}")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
password_authentication do
|
||||
identity_field(:email)
|
||||
hashed_password_field(:hashed_password)
|
||||
end
|
||||
strategies do
|
||||
password :password do
|
||||
identity_field(:email)
|
||||
hashed_password_field(:hashed_password)
|
||||
|
||||
password_reset do
|
||||
sender(fn user, token ->
|
||||
Logger.debug("Password reset request for #{user.email} with token #{inspect(token)}")
|
||||
end)
|
||||
end
|
||||
resettable do
|
||||
sender(fn user, token ->
|
||||
Logger.debug("Password reset request for #{user.email} with token #{inspect(token)}")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
oauth2_authentication do
|
||||
provider_name(:auth0)
|
||||
client_id(&get_config/3)
|
||||
redirect_uri(&get_config/3)
|
||||
client_secret(&get_config/3)
|
||||
site(&get_config/3)
|
||||
oauth2 :auth0 do
|
||||
client_id(&get_config/2)
|
||||
redirect_uri(&get_config/2)
|
||||
client_secret(&get_config/2)
|
||||
site(&get_config/2)
|
||||
|
||||
authorize_path("/authorize")
|
||||
token_path("/oauth/token")
|
||||
user_path("/userinfo")
|
||||
authorization_params(scope: "openid profile email")
|
||||
auth_method(:client_secret_post)
|
||||
end
|
||||
authorize_path("/authorize")
|
||||
token_path("/oauth/token")
|
||||
user_path("/userinfo")
|
||||
authorization_params(scope: "openid profile email")
|
||||
auth_method(:client_secret_post)
|
||||
end
|
||||
end
|
||||
|
||||
tokens do
|
||||
enabled?(true)
|
||||
revocation_resource(Example.Accounts.TokenRevocation)
|
||||
tokens do
|
||||
enabled?(true)
|
||||
token_resource(Example.Accounts.Token)
|
||||
end
|
||||
end
|
||||
|
||||
identities do
|
||||
identity(:unique_email, [:email], pre_check_with: Example.Accounts)
|
||||
identity(:unique_email, [:email],
|
||||
pre_check_with: Example.Accounts,
|
||||
eager_check_with: Example.Accounts
|
||||
)
|
||||
end
|
||||
|
||||
def get_config(path, resource, _opts) do
|
||||
def get_config(path, resource) do
|
||||
value =
|
||||
:ash_authentication_phoenix
|
||||
|> Application.get_env(resource, [])
|
||||
|
|
Loading…
Reference in a new issue