mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-19 13:03:50 +12:00
feat: support new sign in tokens feature on password strategy (#176)
As of version `3.10.5` `ash_authentication` now supports generating and validating short-lived sign in tokens as part of the password sign in flow. This feature seamlessly enables this flow simply by turning on `sign_in_tokens_enabled?` in the password strategy DSL. --------- Co-authored-by: James Harton <james@harton.nz>
This commit is contained in:
parent
2ce0430e8c
commit
903f3a386e
14 changed files with 82 additions and 20 deletions
|
@ -19,8 +19,7 @@ defmodule DevWeb.AuthController do
|
|||
def failure(conn, _activity, reason) do
|
||||
conn
|
||||
|> assign(:failure_reason, reason)
|
||||
|> put_status(401)
|
||||
|> render("failure.html")
|
||||
|> redirect(to: "/sign-in")
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
|
|
@ -224,15 +224,16 @@ defmodule Example.Accounts.User do
|
|||
|
||||
strategies do
|
||||
password :password do
|
||||
identity_field(:email)
|
||||
identity_field :email
|
||||
sign_in_tokens_enabled? true
|
||||
end
|
||||
end
|
||||
|
||||
tokens do
|
||||
enabled?(true)
|
||||
token_resource(Example.Accounts.Token)
|
||||
enabled? true
|
||||
token_resource Example.Accounts.Token
|
||||
|
||||
signing_secret(Application.compile_env(:example, ExampleWeb.Endpoint)[:secret_key_base])
|
||||
signing_secret Application.compile_env(:example, ExampleWeb.Endpoint)[:secret_key_base]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
19
lib/ash_authentication_phoenix/ash_phoenix_errors.ex
Normal file
19
lib/ash_authentication_phoenix/ash_phoenix_errors.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defimpl AshPhoenix.FormData.Error, for: AshAuthentication.Errors.AuthenticationFailed do
|
||||
import Phoenix.HTML.Form, only: [humanize: 1]
|
||||
|
||||
def to_form_error(error) when is_struct(error.strategy, AshAuthentication.Strategy.Password) do
|
||||
[
|
||||
{error.strategy.password_field,
|
||||
"#{humanize(error.strategy.identity_field)} or #{downcase_humanize(error.strategy.password_field)} was incorrect",
|
||||
[]}
|
||||
]
|
||||
end
|
||||
|
||||
def to_form_error(_), do: []
|
||||
|
||||
defp downcase_humanize(value) do
|
||||
value
|
||||
|> humanize()
|
||||
|> String.downcase()
|
||||
end
|
||||
end
|
|
@ -67,6 +67,7 @@ defmodule AshAuthentication.Phoenix.Components.Password do
|
|||
```
|
||||
|
||||
|
||||
|
||||
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
|
||||
"""
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@ defmodule AshAuthentication.Phoenix.Components.Password.Input do
|
|||
<ul class={override_for(@overrides, :error_ul)}>
|
||||
<%= for error <- @errors do %>
|
||||
<li class={override_for(@overrides, :error_li)} phx-feedback-for={input_name(@form, @field)}>
|
||||
<%= @field_label %> <%= error %>
|
||||
<%= error %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -35,7 +35,10 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do
|
|||
alias AshAuthentication.{Info, Phoenix.Components.Password, Strategy}
|
||||
alias AshPhoenix.Form
|
||||
alias Phoenix.LiveView.{Rendered, Socket}
|
||||
import AshAuthentication.Phoenix.Components.Helpers, only: [route_helpers: 1]
|
||||
|
||||
import AshAuthentication.Phoenix.Components.Helpers,
|
||||
only: [route_helpers: 1]
|
||||
|
||||
import Phoenix.HTML.Form
|
||||
import Slug
|
||||
|
||||
|
@ -139,14 +142,40 @@ defmodule AshAuthentication.Phoenix.Components.Password.SignInForm do
|
|||
|
||||
def handle_event("submit", params, socket) do
|
||||
params = get_params(params, socket.assigns.strategy)
|
||||
form = Form.validate(socket.assigns.form, params)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:form, form)
|
||||
|> assign(:trigger_action, form.valid?)
|
||||
if socket.assigns.strategy.sign_in_tokens_enabled? do
|
||||
case Form.submit(socket.assigns.form,
|
||||
params: params,
|
||||
read_one?: true,
|
||||
before_submit: fn changeset ->
|
||||
Ash.Changeset.set_context(changeset, %{token_type: :sign_in})
|
||||
end
|
||||
) do
|
||||
{:ok, user} ->
|
||||
validate_sign_in_token_path =
|
||||
route_helpers(socket).auth_path(
|
||||
socket.endpoint,
|
||||
{socket.assigns.subject_name, Strategy.name(socket.assigns.strategy),
|
||||
:sign_in_with_token},
|
||||
token: user.__metadata__.token
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
{:noreply, redirect(socket, to: validate_sign_in_token_path)}
|
||||
|
||||
{:error, form} ->
|
||||
{:noreply,
|
||||
assign(socket, :form, Form.clear_value(form, socket.assigns.strategy.password_field))}
|
||||
end
|
||||
else
|
||||
form = Form.validate(socket.assigns.form, params)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:form, form)
|
||||
|> assign(:trigger_action, form.valid?)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_params(params, strategy) do
|
||||
|
|
|
@ -47,7 +47,7 @@ defmodule AshAuthentication.Phoenix.Components.Reset.Form do
|
|||
required(:strategy) => AshAuthentication.Strategy.t(),
|
||||
required(:token) => String.t(),
|
||||
optional(:label) => String.t() | false,
|
||||
optional(:overrices) => [module]
|
||||
optional(:overrides) => [module]
|
||||
}
|
||||
|
||||
@doc false
|
||||
|
|
|
@ -2,7 +2,10 @@ defmodule AshAuthentication.Phoenix.Components.SignIn do
|
|||
use AshAuthentication.Phoenix.Overrides.Overridable,
|
||||
root_class: "CSS class for the root `div` element.",
|
||||
strategy_class: "CSS class for a `div` surrounding each strategy component.",
|
||||
show_banner: "Whether or not to show the banner."
|
||||
show_banner: "Whether or not to show the banner.",
|
||||
authentication_error_container_class:
|
||||
"CSS class for the container for the text of the authentication error.",
|
||||
authentication_error_text_class: "CSS class for the authentication error text."
|
||||
|
||||
@moduledoc """
|
||||
Renders sign in mark-up for an authenticated resource.
|
||||
|
|
|
@ -109,6 +109,7 @@ defmodule AshAuthentication.Phoenix.Controller do
|
|||
import Phoenix.Controller
|
||||
import Plug.Conn
|
||||
import AshAuthentication.Phoenix.Plug
|
||||
import AshAuthentication.Phoenix.Controller
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
|
|
|
@ -40,6 +40,9 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
"""
|
||||
|
||||
set :strategy_class, "mx-auth w-full max-w-sm lg:w-96"
|
||||
|
||||
set :authentication_error_container_class, "text-black dark:text-white text-center"
|
||||
set :authentication_error_text_class, ""
|
||||
end
|
||||
|
||||
override Components.Banner do
|
||||
|
@ -129,6 +132,7 @@ defmodule AshAuthentication.Phoenix.Overrides.Default do
|
|||
set :input_class_with_error, """
|
||||
appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md
|
||||
shadow-sm placeholder-gray-400 focus:outline-none border-red-400 sm:text-sm
|
||||
dark:text-black
|
||||
"""
|
||||
|
||||
set :submit_class, """
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -113,7 +113,7 @@ defmodule AshAuthentication.Phoenix.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash_authentication, "~> 3.5"},
|
||||
{:ash_authentication, "~> 3.10"},
|
||||
{:ash_phoenix, "~> 1.1"},
|
||||
{:ash, "~> 2.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,6 +1,6 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "2.6.29", "ab5aee1a0da3d3a5f3f190aadc524da961baf23691e9243dfcd9bf29a3e33555", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 1.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", "8f25f18e3fc9e698732c068b7f5262a21d125a9824619ffd771dbc0d997e8206"},
|
||||
"ash_authentication": {:hex, :ash_authentication, "3.10.4", "c1649865160904aaf8c23ab0bbfed22bbc2c7192ac89c47ce5f22eedd5f66aa4", [:mix], [{:ash, "~> 2.5 and >= 2.5.11", [hex: :ash, repo: "hexpm", optional: false]}, {:assent, "~> 0.2", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 0.4 and >= 0.4.1 or ~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "6e396994085da4d17c0a89ef85a6550792522394f3553c98a7de8deb0dfd8e1c"},
|
||||
"ash_authentication": {:hex, :ash_authentication, "3.10.5", "8a5e9b4b6887c8f6ddd44763dd1ce11fd6db1376e11cfa90dbbc24a72ee2ab2b", [:mix], [{:ash, ">= 2.5.11 and < 3.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:assent, "~> 0.2", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 0.4.1 and < 1.0.0-0 or ~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "6a7d5a64ce8afed4d13231b3964e79870c3de5d1d33b7311eb5173d9a64ceef0"},
|
||||
"ash_phoenix": {:hex, :ash_phoenix, "1.2.12", "83e32d0b192b6815ba2b2766448508cebd957b4a08a96a4273ed41dfb9afcbdb", [:mix], [{:ash, "~> 2.5 and >= 2.5.10", [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", "05f72de031a723d052e85cef8cdff3972e855b53dd5daa3787d13c07cbe017f7"},
|
||||
"assent": {:hex, :assent, "0.2.3", "414d77ea27349dacc980b612e9edeed06c4d64a3df99a0fa8e42e6940ed20c16", [:mix], [{: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", "a39bc5b57920632b003bd175fd58fcb355c10efbe614bba03682ce2a76d4133f"},
|
||||
"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"},
|
||||
|
@ -50,7 +50,7 @@
|
|||
"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.12.2", "2ae55efd149193572e0eb723df7c7a1bda9ab33c43373c82642931dbb2f4e428", [:mix], [], "hexpm", "7ad74ade6fb079c71f29fae10c34bcf2323542d8c51ee1bcd77a546cfa89d59c"},
|
||||
"spark": {:hex, :spark, "1.0.3", "bd31519fdb68247556372e1167bbf3b1db300cde964064975129c0555eb6ae7c", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "5da2c9bc6b1d197be887f3f162316df7ba3227001c31471087bca11d50991bff"},
|
||||
"spark": {:hex, :spark, "1.0.4", "973d9c02fd4a87ca1de89047521fb479ea7d9acd59be4c14afef0aa28e9c2cab", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "56b6d721f458bb683ead7d8870ec6cdabc8a8191feeb3f59357e6ff2adce2907"},
|
||||
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
|
|
|
@ -7,4 +7,8 @@ defmodule Example.Accounts.Token do
|
|||
token do
|
||||
api Example.Accounts
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,7 +78,8 @@ defmodule Example.Accounts.User do
|
|||
password do
|
||||
identity_field(:email)
|
||||
hashed_password_field(:hashed_password)
|
||||
registration_enabled? false
|
||||
registration_enabled? true
|
||||
sign_in_tokens_enabled? true
|
||||
|
||||
resettable do
|
||||
sender(fn user, token, _ ->
|
||||
|
|
Loading…
Reference in a new issue