Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid CSRF token + missing errors #67

Closed
abitdodgy opened this issue Nov 19, 2017 · 11 comments
Closed

Invalid CSRF token + missing errors #67

abitdodgy opened this issue Nov 19, 2017 · 11 comments

Comments

@abitdodgy
Copy link

I'm using drab to fetch a URL and prepopulate a form. However, when I submit the form (vanilla, not using drab for the submission), I get an invalid csrf token. I'm assuming it's because the prefetch request that I did through drab used up the csrf token? What would be the best way to go about refreshing it?

Here's what I'm doing:

<%= form_for @changeset, @action, [novalidate: true], fn f -> %>
  <%= FooWeb.InputHelpers.input f, :url, drab_keyup: "scrape_url", drab_options: "debounce(500)" %>
...

In my commander:

  def scrape_url(socket, %{params: %{"link" => link_params}}) do
    with %Ecto.Changeset{valid?: true, changes: %{url: url}} <- Media.change_link_url(%Media.Link{}, link_params),
      {:ok, %{status_code: 200} = data} <- FurlexHelper.unfurl(url)
    do
      changeset = Media.change_link(%Media.Link{}, Map.put(link_params, "metadata", data))
      Drab.Live.poke socket, "form.html", changeset: changeset
    else
      %Ecto.Changeset{valid?: false} = changeset ->
        Drab.Live.poke socket, "form.html", changeset: changeset
      :error ->
        nil
    end
  end

Another issue I'm running into is not being able to show changeset errors. For example, the else branch above returns an invalid changeset with errors which the form should render, but the form doesn't render out the errors.

@abitdodgy
Copy link
Author

OK, so after some tinkering I found that the missing errors are because the changeset doesn't have an action key set. The issue with csrf remains a problem, however. Are there workarounds I can use?

@grych
Copy link
Owner

grych commented Nov 20, 2017

This is because you are poking @changeset, so Drab re-evaluates the <%= form_for @changeset, ... %> and updates the whole content of the form. This changes a hidden with token as well.

There is no easy workaround, I am afraid.

For the fix, Drab could save all tokens on the client side at load, and re-set it on each poke. Any other ideas?

@abitdodgy
Copy link
Author

The only other way I can think of is to update the form fields individually, I suppose, but that might not be too far removed from a JS/Ajax solution.

@grych
Copy link
Owner

grych commented Nov 20, 2017

Well, in theory I could parse the AST for form_for and, in case there is a @changeset there, treat it differently. But I don't want to :) this will lead to a lot of code for just this case.

Or create Drab version of form_for. And keep it compatible with Phoenix forever...

@abitdodgy
Copy link
Author

Yeah, I'm not sure it's worth it, but it would be nice to have drab work with forms in that way. I'm not sure form_for changes all that much. Maybe a separate add on lib?

@grych
Copy link
Owner

grych commented Nov 20, 2017

I will just first try to save the csfr before updating, as it is the simplest solution. Thanks for all the feedback!

@abitdodgy
Copy link
Author

Well, I just thought of something. I can split the form into two: One for Drab and one for the record. The Drab form would submit and update the changeset of the record form. Since the record form has not been submitted, it should work fine. Or is that, simply by Drab updating the changeset, the CSRF error will happen?

@grych
Copy link
Owner

grych commented Nov 20, 2017

If you use the "normal html", not submitting button, and just give it a drab-click handler, it will work. Drab is not checking csrf at all (as it is going via connected websocket).

@abitdodgy
Copy link
Author

Right. My intention though is to use Drab to preload the form, and then submit the form via normal HTML. In that case I would split the form into two pieces. The drab one continues to work on the on keyup handle, uses the commanded to fetch data and prepopulate the changeset for the second form. But what you are saying is that if Drab updates the second changeset, submitting the form over normal html would result in the same error?

@grych
Copy link
Owner

grych commented Nov 20, 2017

You can always use nodrab option to make the part not drabbed. See https://hexdocs.pm/drab/Drab.Live.html#module-avoiding-using-drab

@grych
Copy link
Owner

grych commented Feb 15, 2018

Hi @abitdodgy ,
I've decided not to do any special cases for updating a form via Drab. It would be too complicated and may cause new errors. You will need to deal with this yourself. Actually it is not very complicated, you just need to save the token before you re-render the form, and then set it back:

# safe the existing token
{:ok, %{"value" => token}} = query_one socket, "form > input[name='_csrf_token']", :value
# re-render the form
poke socket, "#form_holder", innerHTML: Phoenix.View.render(MyView, "form.html", assign: "value")
# set the token back
set_attr socket, "form > input[name='_csrf_token']", value: token

I am going to put this on Drab's wiki. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants