Hey I a trying to make a login system for my phoenix app, but I am having an error on successful login attempt: # protocol Enumerable not implemented for %Userteam1.Web.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1, inserted_at: ~N[2018-08-19 09:10:38.661295], name: "bence", password: nil, password_hash: "$2b$12$EEH/mgGuM1GyKHkUSIJG..THKJ6W/0iN/tWH6FiikZ4dRjGqPwt3G", role: #Ecto.Association.NotLoaded<association :role is not loaded>, role_id: 1, team: #Ecto.Association.NotLoaded<association :team is not loaded>, team_id: nil, updated_at: ~N[2018-08-19 09:10:38.661303]}. This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Stream
my controller for login looks like this:
def create(conn, %{"session" => %{"name" => name, "password" => password}}) do
case Userteam1.Web.name_password_auth(name, password) do
{:ok, user} ->
IO.inspect(user)
conn
|> put_flash(:info, "Successfully signed in")
|> Guardian.encode_and_sign(user)
|> redirect(to: user_path(conn, :index))
{:error, _reason} ->
conn
|> put_flash(:error, "Invalid name or password")
|> render("new.html")
end
end
and my schema that it is trying to load looks like this:
schema "users" do
field(:name, :string)
field(:password_hash, :string)
field(:password, :string, virtual: true)
belongs_to(:role, Userteam1.Role)
belongs_to(:team, Userteam1.Team)
timestamps()
end
ok found something, I changed it into this: |> Guardian.Plug.sign_in(user)
but no I am having a new error: (Guardian.Plug.UnauthenticatedError) {:error, :secret_not_found}
And, I"m calling sign_in() with the correct types for the arguments. Iβm stuck.
Hereβs my config.exs:
use Mix.Config
config :dog,
ecto_repos: [Dog.Repo]
config :dog, Dog.Repo,
datbase: "dog",
username: "7stud",
password: "",
hostname: "localhost",
port: "5432"
config :dog, Dog.UserManager.Guardian,
issuer: "dog",
secret_key: "e8UPW0Doh0iBonm8ZK0RJVzaAA8r+Jgw4t3lVEoB1SiByke/V9cxcz//1iPkpVrq"
# Configures the endpoint
config :dog, DogWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "xpVFSaAFJZ8xUEhWLrzT/sTvL/Z6y+mSvfobiKiKanWexNwL2Wj2EbRXr7V9fQSj",
render_errors: [view: DogWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: Dog.PubSub, adapter: Phoenix.PubSub.PG2]
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
Iβm using a UserManager context for the public interface:
defmodule Dog.UserManager do
@repo Dog.Repo
alias Dog.UserManager.User
alias Argon2
import Ecto.Query, only: [from: 2]
def list_users() do
@repo.all(User)
end
def get_user(id) do
@repo.get!(User, id)
end
def change_user(%User{}=user) do
User.changeset(user, %{})
end
def insert_user(attrs) do
%User{}
|> User.changeset(attrs)
|> @repo.insert()
end
def delete_user(%User{}=user) do
@repo.delete(user)
end
def authenticate_user(username, plain_text_password) do
query = from u in User, where: u.username == ^username
case @repo.one(query) do
nil ->
Argon2.no_user_verify()
{:error, :invalid_credentials}
user ->
if Argon2.verify_pass(plain_text_password, user.password) do
{:ok, user}
else
{:error, :invalid_credentials}
end
end
end
end
def login_reply({:ok, user}, conn) do
put_flash(conn, :info, "Welcome back!")
new_conn = Guardian.Plug.sign_in(conn, Guaridan, user, %{}, [])
redirect(new_conn, to: "/secret")
end
def login_reply({:error, reason}, conn) do
conn
|> put_flash(:error, to_string(reason))
|> new(%{})
end
And, I got this warning:
~/phoenix_apps/dog$ mix phx.server
warning: function Dog.UserManager.Guardian.Plug.sign_in/5 is undefined or private. Did you mean one of:
* sign_in/2
* sign_in/3
* sign_in/4
lib/dog_web/controllers/session_controller.ex:28
...
...
Seems like youβve confused one with the other because of
alias Dog.{UserManager, UserManager.User, UserManager.Guardian}
And, I checked that line to make sure that Guardian.Plug was indeed equivalent to Dog.UserManager.Guardian.Plug! All that code is from the official Guardian βGetting Started Tutorialβ.
And, the route problem I was having was due to the tutorial alternately using β/protectedβ and β/secretβ to refer to the Guardian protected route.
By the way, things work when I do:
alias Dog.{UserManager, UserManager.User, UserManager.Guardian}
...
new_conn = Guardian.Plug.sign_in(conn, user)
So, I guess I was looking at the wrong docs??? This is what my lib/dog/user_manager/guardian.ex file looks like:
defmodule Dog.UserManager.Guardian do
use Guardian, otp_app: :dog
alias Dog.UserManager
def subject_for_token(user, _claims) do
# You can use any value for the subject of your token but
# it should be useful in retrieving the resource later, see
# how it being used on `resource_from_claims/1` function.
# A unique `id` is a good subject, a non-unique email address
# is a poor subject.
{:ok, to_string(user.id)}
end
def resource_from_claims(%{"sub" => id}) do
# Here we'll look up our resource from the claims, the subject can be
# found in the `"sub"` key. In `above subject_for_token/2` we returned
# the resource id so here we'll rely on that to look it up.
case UserManager.get_user(id) do
nil -> {:error, :user_not_found}
user -> {:ok, user}
end
end
end
I guess the line:
use Guardian, otp_app: :dog
injects a Plug module? I looked at the source code for the Guardian module:
defmacro __using__(opts \\ []) do
otp_app = Keyword.get(opts, :otp_app)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
@behaviour Guardian
if Code.ensure_loaded?(Plug) do
__MODULE__
|> Module.concat(:Plug)
|> Module.create( ## CREATES THE Dog.UserManager.Guardian.Plug MODULE
quote do
use Guardian.Plug, unquote(__MODULE__)
end,
Macro.Env.location(__ENV__)
)
end
And Guardian.Plugβs __using__() function looks like this: