fix(Strategy.Password): Reset tokens are single use.

When a token is successfully used to reset a password then it is immediately revoked so that it cannot be used again.

Closes #624.
This commit is contained in:
James Harton 2024-04-10 11:19:41 +12:00
parent 2da79ce1d3
commit ac24e83b2e
Signed by: james
GPG key ID: 90E82DAA13F624F4
2 changed files with 24 additions and 1 deletions

View file

@ -7,7 +7,7 @@ defmodule AshAuthentication.Strategy.Password.Actions do
"""
alias Ash.{Changeset, Error.Invalid.NoSuchAction, Query, Resource}
alias AshAuthentication.{Errors, Info, Jwt, Strategy.Password}
alias AshAuthentication.{Errors, Info, Jwt, Strategy.Password, TokenResource}
@doc """
Attempt to sign in a user.
@ -236,6 +236,11 @@ defmodule AshAuthentication.Strategy.Password.Actions do
}
})
|> Changeset.for_update(resettable.password_reset_action_name, params)
|> Changeset.after_action(fn _changeset, record ->
token_resource = Info.authentication_tokens_token_resource!(resource)
:ok = TokenResource.revoke(token_resource, token)
{:ok, record}
end)
|> Ash.update(options)
else
{:error, %Changeset{} = changeset} -> {:error, changeset}

View file

@ -5,6 +5,7 @@ defmodule AshAuthentication.Strategy.Password.ActionsTest do
alias AshAuthentication.{
Errors.AuthenticationFailed,
Errors.InvalidToken,
Info,
Jwt,
Strategy.Password,
@ -246,5 +247,22 @@ defmodule AshAuthentication.Strategy.Password.ActionsTest do
assert user.hashed_password != updated_user.hashed_password
assert strategy.hash_provider.valid?(new_password, updated_user.hashed_password)
end
test "the token can only be used once" do
user = build_user()
{:ok, strategy} = Info.strategy(Example.User, :password)
assert {:ok, token} = Password.reset_token_for(strategy, user)
new_password = password()
params = %{
"reset_token" => token,
"password" => new_password,
"password_confirmation" => new_password
}
assert {:ok, _} = Actions.reset(strategy, params, [])
assert {:error, %InvalidToken{}} = Actions.reset(strategy, params, [])
end
end
end