Elixir version (elixir -v):
Elixir 1.7.3 (compiled with Erlang/OTP 21)
Phoenix version (mix deps):
1.4.2
NodeJS version (node -v):
v10.8.0
NPM version (npm -v):
6.2.0
Operating system:
macOS Mojave 10.14.3
For all form inputs to retain state when monitored with phx_change event.
The value of input[type=password] fields are reset when other fields are edited and trigger a phx_change event. This can be prevented by adding value: @changeset.changes[:password] to a password_input f, :password input field, forcing the value to be rendered in HTML resulting in a successful diff.
This isn't the behaviour I see in the examples repo. Can you share your view and template code to recreate this? Thanks!
This is the minimum app that I could reproduce it with:
https://github.com/jwbrew/phoenix_live_view_demo
Screen recording here: Screen Recording 2019-03-21 at 08.46.14.zip
Hopefully it does the same for you!
Thanks for all your work on this. It's completely awesome 🔥
I have the same issue. If I start typing in the password_confirmation field the password field is cleared. I think it is standard behaviour of the browser to empty password fields after they have been submitted, maybe that is causing this behaviour.
I encountered the same issue. I'm assuming it's because of the following documented behaviour in
https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#password_input/3
For security reasons, the form data and parameter values are never re-used in password_input/3. Pass the value explicitly if you would like to set one.
My fix was similar to @jwbrew
<%= password_input f, :password, value: input_value(f, :password) %>
Is there a way to detect that the current request is a LiveView request, so we could add this as a check in our Phoenix Template (or even add as an optional behaviour in Phoenix.HTML.Form.html#password_input/3 itself ?)
I create a module in my project under my_app_web/live/form_helpers and import it in MyAppWeb.web/2 as a current workaround.
defmodule MyAppWeb.Live.FormHelper do
import Phoenix.HTML.Form
import Phoenix.HTML.Tag, only: [tag: 2]
def live_view_passowrd_input(form, field, opts \\ []) do
opts =
opts
|> Keyword.put_new(:type, "password")
|> Keyword.put_new(:id, input_id(form, field))
|> Keyword.put_new(:name, input_name(form, field))
|> Keyword.put_new(:value, input_value(form, field))
tag(:input, opts)
end
end
would like to hear what other people think how should Liveview fix this problem as current it shares Phoenix.HTML. Btw what's the security concern that value is not included by default in password_input as text_input?
The best we can do today is document the requirement of an explicit :value option. If the value is provided, everything works as expected:
<%= password_input f, :password, value: input_value(f, :password) %>
<%= password_input f, :password_confirmation, value: input_value(f, :password_confirmation) %>
<%= error_tag f, :password %>
<%= error_tag f, :password_confirmation %>
You can have a live_password_input function which does this for you as bruteforcecat has done. Thanks!
I am using LiveView version 0.7.1 and I am having an even weirder behavior: https://www.loom.com/share/5936805566c44d75acb94f83f86a5fe2
This is my changeset:
defmodule Turing.Accounts.Credential do
use Ecto.Schema
import Ecto.Changeset
alias __MODULE__
alias Turing.Accounts.User
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "credentials" do
field(:email, :string)
field(:password, :string)
belongs_to(:user, User)
timestamps()
end
@doc false
def changeset(%Credential{} = credential, attrs) do
credential
|> cast(attrs, [:email, :password, :user_id])
|> validate_required([:email, :password])
|> validate_format(:email, ~r/@/)
|> validate_length(:password, min: 6, max: 100)
|> validate_confirmation(:password, message: "does not match password")
|> foreign_key_constraint(:user_id)
|> hash_password()
|> downcase_email()
|> unique_constraint(:email)
end
defp downcase_email(%Ecto.Changeset{valid?: false} = changeset), do: changeset
defp downcase_email(%Ecto.Changeset{valid?: true} = changeset) do
update_change(changeset, :email, &String.downcase/1)
end
defp hash_password(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password, Bcrypt.hash_pwd_salt(pass))
_ ->
changeset
end
end
end
my form:
<%= f = form_for @changeset,
"#",
[
phx_change: :validate,
phx_submit: :sign_in,
autocomplete: :off
]
%>
<%= label f, :email %>
<%= text_input f, :email %>
<%= error_tag f, :email %>
<%= label f, :password %>
<%= password_input f, :password, value: input_value(f, :password) %>
<%= error_tag f, :password %>
<%= password_input f, :password_confirmation, value: input_value(f, :password_confirmation) %>
<%= error_tag f, :password_confirmation %>
<%# submit %>
<div>
<%= submit "Sign In", phx_disable_with: "Loading ..." %>
</div>
</form>
live view module:
defmodule TuringWeb.Live.Session do
use Phoenix.LiveView
alias Turing.{Accounts, Accounts.Credential}
def mount(_session, socket) do
{:ok, fetch(socket)}
end
def render(assigns) do
TuringWeb.SessionView.render("index.html", assigns)
end
def fetch(socket) do
assign(socket, %{
changeset: Accounts.change_credential(%Credential{})
})
end
def handle_event("validate", %{"credential"=> params}, socket) do
changeset =
%Credential{}
|> Accounts.change_credential(params)
|> Map.put(:action, :insert)
{:noreply, assign(socket, changeset: changeset)}
end
end
After I edit both fields and click out, the password strangely auto-fills with some weird data :|
Can you try master and let us know how it goes? (Make sure you update and blow away/reinstall node deps). Master includes ref based tracking of client/server events so your issue may be already taken care of
That might solve the issue I think. Thanks for quickly responding to my comment. After reading users' experience regarding handling session state with LiveView (https://elixirforum.com/t/how-to-manage-session-state-with-live-view/21196), I've decided to use static views for the authentication forms and stick with LiveView for the Chat (which requires an interactive interface) I am working on.
It might be possible to design an entire authentication layer using LiveView but as it's way faster and simple with static views, I'll go for it (without LiveView) for now as I need to save time on my side project.
Static views are the only place you can set the cookie session, so that is a good plan for the signing to POST to. Nonetheless can you try master to see if this particular issue was fixed? Thanks!
Unfortunately, the issue persists. I pushed a branch which you can hopefully reproduce it on your end if you want to give it try https://github.com/wood-archer/turing/tree/liveview/sign-in
The page in question is localhost:4000/sign_in
So this isn't a bug in LV 0.7 or master. You are hashing the password if the changeset is valid and matches:
defp hash_password(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password, Bcrypt.hash_pwd_salt(pass))
_ ->
changeset
end
end
So the odd behavior you see in the password field is actually because the password value becomes the hashed value, which is a longer string :)
You should make the password a virtual field, and use password_hash or similar for the db field. Make sense?
It totally makes sense. It's all about the changeset being valid and hashing the password. That happens because I am passing the hashed password to the input value. Thanks for finding what it was.
I was relying on the function validate_confirmation/3 but it sounds better to do it manually.
So in that respect, you should still be able to use LV for login, but your sign-in click will either need to be a regular form submit to a controller to put the session, or what I've seen other folks do is handle the signing click in the LV, and redirect to a controller with a token in the URL, which does the put_session dance. The former is easier, but the latter gives you more control of handling the submit click in the LV should you want to do other work.
I was relying on the function
validate_confirmation/3but it sounds better to do it manually.
You can rely on this for sure, but you should have a password_hash field that put the Bcrypted hash value in, not the password field, which can become virtual (not stored in the DB)
Password and password confirmation definitely can be virtual fields while the password_hash a database field.
what I've seen other folks do is handle the signing click in the LV, and redirect to a controller with a token in the URL
Interesting ...
It looks like I can pass the token in the URL and then verify it in the other controller:
# encode a token for a resource
{:ok, token, claims} = MyApp.Guardian.encode_and_sign(resource)
# decode and verify a token
{:ok, claims} = MyApp.Guardian.decode_and_verify(token)
I'll give a last try and then decide if it worth the effort. At least the register and sign in pages would be fine to use live view.
I am running out of the subject of this issue, sorry. I should ask around if I need any more help on this. Thanks for the amazing support!
I figured out how to solve my issues and will keep the login with LiveView. Will follow the "token in the URL" approach like this guy did https://elixirforum.com/t/from-liveview-to-normal-views/23774/2
I am facing the same issue:
I edit email, I lost password, I edit password, I lost email
Most helpful comment
This is the minimum app that I could reproduce it with:
https://github.com/jwbrew/phoenix_live_view_demo
Screen recording here: Screen Recording 2019-03-21 at 08.46.14.zip
Hopefully it does the same for you!
Thanks for all your work on this. It's completely awesome 🔥