fix(Confirmation): Only allow the confirmation token to be used once. (#623)

Fixes a potential issue where the confirmation token can be used multiple times, potentially opening a replay attack.

Closes #618
This commit is contained in:
James Harton 2024-04-09 09:32:37 +12:00 committed by GitHub
parent 1fdc3af0b3
commit c22439f48f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 6 deletions

View file

@ -24,7 +24,8 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
with {:ok, domain} <- Info.domain(strategy.resource),
{:ok, token} <- Map.fetch(params, "confirm"),
{:ok, %{"sub" => subject}, _} <- Jwt.verify(token, strategy.resource),
{:ok, user} <- AshAuthentication.subject_to_user(subject, strategy.resource, opts) do
{:ok, user} <- AshAuthentication.subject_to_user(subject, strategy.resource, opts),
{:ok, token_resource} <- Info.authentication_tokens_token_resource(strategy.resource) do
user
|> Changeset.new()
|> Changeset.set_context(%{
@ -33,6 +34,12 @@ defmodule AshAuthentication.AddOn.Confirmation.Actions do
}
})
|> Changeset.for_update(strategy.confirm_action_name, params)
|> Changeset.after_action(fn _changeset, record ->
case TokenResource.revoke(token_resource, token) do
:ok -> {:ok, record}
{:error, reason} -> {:error, reason}
end
end)
|> domain.update(opts)
else
:error -> {:error, InvalidToken.exception(type: :confirmation)}

View file

@ -5,7 +5,14 @@ defmodule AshAuthentication.AddOn.Confirmation.ActionsTest do
import Ecto.Query
alias Ash.Changeset
alias AshAuthentication.{AddOn.Confirmation, AddOn.Confirmation.Actions, Info, Jwt}
alias AshAuthentication.{
AddOn.Confirmation,
AddOn.Confirmation.Actions,
Errors.InvalidToken,
Info,
Jwt
}
describe "confirm/2" do
test "it returns an error when there is no corresponding user" do
@ -51,12 +58,25 @@ defmodule AshAuthentication.AddOn.Confirmation.ActionsTest do
DateTime.to_unix(DateTime.utc_now()),
1.0
# I don't know why this is failing. I even tried changing
# `AshAuthentication.AddOn.Confirmation.ConfirmChange` to use
# `Ash.Changeset.force_change_attributes/2` to no avail.
# I can see the updated_at being set, but not the new username.
assert to_string(confirmed_user.username) == new_username
end
test "the same token can't be used more than once" do
{:ok, strategy} = Info.strategy(Example.User, :confirm)
user = build_user()
new_username = username()
changeset =
user
|> Changeset.for_update(:update, %{"username" => new_username})
{:ok, token} = Confirmation.confirmation_token(strategy, changeset, user)
assert {:ok, _user} = Actions.confirm(strategy, %{"confirm" => token}, [])
assert {:error, %InvalidToken{}} = Actions.confirm(strategy, %{"confirm" => token}, [])
end
end
describe "store_changes/3" do