I create authentication for users resource and it works fine but now i want to use the authenticate function from user_controller.ex to project_controller.ex.
If i copy authenticate private function from user_controller to project_controller than authentication works on projects resource but i don't want to duplicate this authenticate function in every controller. i need to know what is the best approach to dry this code. i think the router is the good place to add the authenticate plug but i need to know where should i add the code.
defmodule Auth.Router do
use Auth.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Auth.Auth, repo: Auth.Repo
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", Auth do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/users", UserController
resources "/sessions", SessionController, only: [:new, :create, :delete]
resources "/projects", ProjectController
end
end
defmodule Auth.UserController do
use Auth.Web, :controller
plug :authenticate when action in [:index, :show]
alias Auth.User
plug :scrub_params, "user" when action in [:create, :update]
def index(conn, _params) do
users = Repo.all(User)
render(conn, "index.html", users: users)
end
def new(conn, _params) do
changeset = User.changeset(%User{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user" => user_params}) do
changeset = User.registration_changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} ->
conn
|> Auth.Auth.login(user)
|> put_flash(:info, "#{user.username} created successfully.")
|> redirect(to: user_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
user = Repo.get!(User, id)
render(conn, "show.html", user: user)
end
def edit(conn, %{"id" => id}) do
user = Repo.get!(User, id)
changeset = User.changeset(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Repo.get!(User, id)
changeset = User.changeset(user, user_params)
case Repo.update(changeset) do
{:ok, user} ->
conn
|> put_flash(:info, "User updated successfully.")
|> redirect(to: user_path(conn, :show, user))
{:error, changeset} ->
render(conn, "edit.html", user: user, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = Repo.get!(User, id)
Repo.delete!(user)
conn
|> put_flash(:info, "User deleted successfully.")
|> redirect(to: user_path(conn, :index))
end
defp authenticate(conn, _opts) do
if conn.assigns.current_user do
conn
else
conn
|> put_flash(:error, "You must be login to access that page.")
|> redirect(to: page_path(conn, :index))
|> halt()
end
end
end
defmodule Auth.User do
use Auth.Web, :model
schema "users" do
field :username, :string
field :password_hash, :string
field :password, :string, virtual: true
timestamps
end
def changeset(model, params \\ :empty) do
model
|> cast(params, ~w(username), [])
|> validate_length(:username, min: 3, 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, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))
_ ->
changeset
end
end
end
defmodule Auth.Auth do
import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2]
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(Auth.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(Auth.User, username: username)
cond do
user && checkpw(given_pass, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
{:error, :not_found, conn}
end
end
def logout(conn) do
# configure_session(conn, drop: true)
delete_session(conn, :user_id)
end
end
defmodule Auth.ProjectController do
use Auth.Web, :controller
plug :authenticate when action in [:index, :new, :show]
alias Auth.Project
plug :scrub_params, "project" when action in [:create, :update]
def index(conn, _params) do
projects = Repo.all(Project)
render(conn, "index.html", projects: projects)
end
def new(conn, _params) do
changeset = Project.changeset(%Project{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"project" => project_params}) do
changeset = Project.changeset(%Project{}, project_params)
case Repo.insert(changeset) do
{:ok, _project} ->
conn
|> put_flash(:info, "Project created successfully.")
|> redirect(to: project_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
project = Repo.get!(Project, id)
render(conn, "show.html", project: project)
end
def edit(conn, %{"id" => id}) do
project = Repo.get!(Project, id)
changeset = Project.changeset(project)
render(conn, "edit.html", project: project, changeset: changeset)
end
def update(conn, %{"id" => id, "project" => project_params}) do
project = Repo.get!(Project, id)
changeset = Project.changeset(project, project_params)
case Repo.update(changeset) do
{:ok, project} ->
conn
|> put_flash(:info, "Project updated successfully.")
|> redirect(to: project_path(conn, :show, project))
{:error, changeset} ->
render(conn, "edit.html", project: project, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
project = Repo.get!(Project, id)
# Here we use delete! (with a bang) because we expect
# it to always work (and if it does not, it will raise).
Repo.delete!(project)
conn
|> put_flash(:info, "Project deleted successfully.")
|> redirect(to: project_path(conn, :index))
end
# defp authenticate(conn, _opts) do
# if conn.assigns.current_user do
# conn
# else
# conn
# |> put_flash(:error, "You must be login to access that page.")
# |> redirect(to: page_path(conn, :index))
# |> halt()
# end
# end
end
This is a fairly common pattern. First you need your Authenticate plug:
defmodule Auth.Plug.Authenticate do
@behaviour Plug
import Plug.Conn
import Phoenix.Controller, only: [put_flash: 3, redirect: 2]
def init(opts), do: opts
def call(conn, _opts) do
if conn.assigns.current_user do
conn
else
conn
|> put_flash(:error, "You must be login to access that page.")
|> redirect(to: Auth.Router.Helpers.page_path(conn, :index))
|> halt()
end
end
end
Then in your router you can do:
defmodule Auth.Router do
use Auth.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Auth.Auth, repo: Auth.Repo
end
pipeline :authenticated do
plug Auth.Plug.Authenticate, repo: Auth.Repo
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", Auth do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/sessions", SessionController, only: [:new, :create]
end
scope "/", Auth do
pipe_through [:browser, :authenticated]
resources "/users", UserController
resources "/sessions", SessionController, only: [:delete]
resources "/projects", ProjectController
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With