can.ex icon indicating copy to clipboard operation
can.ex copied to clipboard

Simplified authorization for Phoenix framework

Stories in Ready

Can

Dead simple, fire and forget authorization kit for the Phoenix framework

Build Status Hex Downloads Hex Version

Installation

Add Can to your list of dependencies in mix.exs:

def deps do
  [{:can, "~>0.0.5"}]
end

Usage

Inferring policy from controller name and action

# in web.ex
defmodule MyApp.Web do
  def controller() do
    quote do
      # ...other definitions
      import Can

      plug Can.ContextProvider
    end
  end
end

# in post_controller.ex
defmodule MyApp.PostController do
  use MyApp.Web, :controller

  def show(conn, %{"id" => id}) do
    post = Repo.get(Post, id)

    conn
    |> can!(post: post)
    |> render("show.html", post: post)
  end
end

# in post_policy.ex
defmodule MyApp.PostPolicy do
  def show(conn, context) do
    context[:post].author_id == Auth.current(conn).id
  end
end

# in error_view.ex
defmodule MyApp.ErrorView do
  use MyApp.Web, :view

  # ...other definitions
  def render("401.html", %{reason: %{context: %{post: post}}}) do
    "You are not authorized to view #{post.id}"
  end
end

Overriding inferred policy or action with plug

# in post_controller.ex
defmodule MyApp.PostController do
  use MyApp.Web, :controller

  plug Can.ContextProvider, policy: __MODULE__, action: :show_post

  def show(conn, %{"id" => id}) do
    post = Repo.get(Post, id)

    conn
    |> can!(post: post)
    |> render("show.html", post: post)
  end

  def show_post(conn, context) do
    context[:post].author_id == Auth.current(conn).id
  end
end

Overriding inferred policy or action with %Conn{} transformation

# in post_controller.ex
defmodule MyApp.PostController do
  use MyApp.Web, :controller

  def show(conn, %{"id" => id}) do
    post = Repo.get(Post, id)

    conn
    |> put_policy(__MODULE__)
    |> can!(:show_post, post: post)
    |> render("show.html", post: post)
  end

  def show_post(conn, context) do
    context[:post].author_id == Auth.current(conn).id
  end
end
# in post_controller.ex
defmodule MyApp.PostController do
  use MyApp.Web, :controller

  def show(conn, %{"id" => id}) do
    post = Repo.get(Post, id)

    conn
    |> put_policy(__MODULE__)
    |> put_action(:show_post)
    |> can!(post: post)
    |> render("show.html", post: post)
  end

  def show_post(conn, context) do
    context[:post].author_id == Auth.current(conn).id
  end
end

Usage in views

# in web.ex
defmodule MyApp.Web do
  def view() do
    quote do
      # ...other definitions
      import Can
    end
  end
end

# in layout.html.slim
.nav_menu
  = if can?(@conn, :index) do
    = link_to("POSTS", post_path(@conn, :index))
  = if can?(@conn, :superadmin?) do
    = link_to("SETTINGS", setting_path(@conn, :index))