Ash Policies around :change_password action
I have a
User
resource with (mostly) the default update :change_password
action generated by the igniter installer,
And a LiveComponent (attached) to provide a form to allow the user to change their password. When I start typing in the form, the validation event is triggered, and the LiveView crashes because of an Ash error,
For the LiveViews provided by AshAuthenticationPhoenix, the AshAuthentication.Checks.AshAuthenticationInteraction
bypass is satisfied, but here I am running actions directly from my code. What would be a safe way to allow access to the password fields for only the password-changen workflow, without exposing it entirely?29 Replies
I suppose allowing access if the actor is the user we are trying to run the action on, and only for this action. Would that be narrow enough to not compromise security?
Solution
Yes, you could set up a policy like that 👍
Ergh, jumped the gun a bit there. My attempt at implementing this has not worked.
I added a policy
And a field policy,
But the action is still getting a forbidden field.
What other field policies do you have?
Ah
Its how you're reading the record
Whatever read action you're using to bring in the record is causing the problem
you'll need to load the hashed password back from the db for example
How should I do that?
Actually, I forget if this is the case, but the hashed password may actually be available in the
original_value
field of the hashed password, only hidden
I don't think so IIRC thats reserved for a specific purpose
but otherwise you'll need to do something like pattern match on the value in a before_action
hook and Ash.load(...)
i.e %{changeset | data: Ash.load!(changeset.data, :hashed_password, authorize?: false}
if its a forbidden fieldLooks like it will need to be a
before_transaction
, because validations run before before_action
.
https://hexdocs.pm/ash/actions.html#hook-execution-orderactually
it will need to not be a hook at all
because
before_transaction
also runs after validationsAm I reading the docs wrong, then?
I have it working with
assign_form/1
in the above component changed to this.
Should probably do this properly with policies now.
I have gone with a similar pattern to AshAuthentication's check.
I would set the context in the form
but load the hashed_password in the action
that way if you ever call that action you don't have to also load hashed_password in the caller
I thought that couldn't be done because all hooks are run after validations.
Not all change logic happens in a hook
Actually...your way is better for now
Where else can you change the changeset outside of hooks that also happens before validations?
just in the body of a change function
The exact same changeset function does not work if I put it in a change function in the action.
If I tack
|> IO.inspect(pretty: true, structs: false)
on to the end, I see that hashed_password
on changeset.data
is a forbidden field.Did you put it before the validation?
Yes
Hm...I'd have to investigate
Your way works as a reasonable approach though.
Yeah, I'm happy with it for now, but it feels like it could be a bit cleaner still.
Thanks for your help!
This actually doesn't work either. I think when I thought it had worked, my liveview was in the old state from when I had
authorize?: false
.
So why is this field policy bypass insufficient?
It's about the read action not the update action
The read action is what needs to allow viewing the changing password in this case
Right, but shouldn't that policy make this load work?
I confirmed that the
match?/3
clause that returns true is being hit by adding some logging.
https://discord.com/channels/711271361523351632/1398837010848026644/1398884659286249593Yes I think so
Is the data coming back without hashed passed filled in?
Yes.
🤔 🤔 🤔 weird
This might be a bug then 😢
Can you add
require_atomic? false
to the update action?
It may already have it thoguhYep, already there.
could you reproduce this perhaps and I can look into it?
No need. Found the culprit!
In
field_policies
, private_fields :hide
needs to be private_fields :include
.