Hello! I have been learning Elixir for the last couple of months, and this past week I have started learning Phoenix with Programming Phoenix by Chris McCord, Bruce Tate, and Jose Valim.
Elixir Version: 1.7.3
Phoenix Version: 1.2.5
I am currently stuck on Creating Users in Chapter 5 (Authenticating Users). In Rumbl.UserController.create()
, a user was successfully added to the database by passing in the according changeset
to Rumbl.Repo.insert(changeset)
. When checking the Ecto database with Rumbl.Repo.all(Rumbl.User)
, the only populated fields within the User
structs are for name
and username
. I don’t expect password
to be populated as that field was set to virtual in the schema, no problem there. However, the issue is the password_hash field
is not populated (set to nil
). Because of this, I cannot use Comeonin.Bcrypt.checkpw()
to properly authenticate a user.
I have been struggling with this issue for a couple of hours now and can’t seem to find the right resource to help solve the issue.
Rumbl.UserController.create()
def create(conn, %{"user" => user_params}) do
user_params = for {key, val} <- user_params, into: %{}, do: {String.to_atom(key), val}
changeset = User.registration_changeset(%User{}, user_params)
IO.inspect(changeset)
case Repo.insert(changeset) do
{:ok, user} ->
IO.inspect(user)
conn
|> Rumbl.Auth.login(user)
|> put_flash(:info, "#{user.name} created!")
|> redirect(to: user_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
Rumbl.User
defmodule Rumbl.User do
use Rumbl.Web, :model
schema "users" do
field :name, :string
field :username, :string
field :password, :string, virtual: true # virtual fields are NOT persisted to the database
field :password_hash, :string
timestamps()
end
def changeset(model, params \\ :invalid) do
model
|> cast(params, ~w(name username), [])
|> validate_length(:username, min: 1, max: 20)
end
def registration_changeset(model, params) do
model
|> changeset(params)
|> cast(params, ~w(password), [])
|> validate_length(:password, min: 6, max: 100)
|> put_pass_hash()
end
defp put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :pasword_hash, Comeonin.Bcrypt.hashpwsalt(pass))
_ ->
changeset
end
end
end
Rumbl.Auth
defmodule Rumbl.Auth do
import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
def init(opts) do
Keyword.fetch!(opts, :repo)
end
def call(conn, repo) do
user_id = get_session(conn, :user_id)
user = user_id && repo.get(Rumbl.User, user_id)
assign(conn, :current_user, user)
end
def login(conn, user) do
conn
|> assign(:current_user, user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
end
def login_by_username_and_pass(conn, username, given_pass, opts) do
repo = Keyword.fetch!(opts, :repo)
user = repo.get_by(Rumbl.User, username: username)
IO.puts("Given Pass: #{given_pass}")
IO.puts("Password Hash: #{inspect user.password_hash}")
cond do
user && checkpw(given_pass, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
dummy_checkpw()
{:error, :not_found, conn}
end
end
end