Cap

Hex.pm Hex.pm

Cap is Central Authentication Plug for Phoenix, access control library with Role-based access control (RBAC) and Attribute-based access control (ABAC)

How to use

Add the package as a dependency in your Elixir project using something along the lines of:

def deps do
  [
    {:cap, "~> 0.2.0"}
  ]
end

In your configuration you can set values for RBAC directly, eg

config :cap,
       effect: :deny,         
       #Allow Root to any
       exception: :root, 
       policy: %{
         nil: %{
           # Deny Anonymous use to any action in any Controller
           :* => :* 
         },
         admin: %{
           # Deny Admin use to any action in UserController
           ThetaWeb.UserController => :* 
         }, 
         mod: %{
           # Deny Mod use to action :update and :delete in ArticleController
           ThetaWeb.CMS.ArticleController => [:update, :delete],
           # Deny Mod use to any action in UserController
           ThetaWeb.UserController => :*
         },
         user: %{
           # Deny User use to any action in any Controller
           :* => :*
         },
       },
       # secret_key for encrypt, decrypt value in session
       secret_key: "AMlTnnYyOp3EWUbwSTawScMyF9IQoVYs" 

Add plug to scope in pipeline

pipeline :admin do
    plug :put_layout, {ThetaWeb.LayoutView, "layout_admin.html"}
    plug Cap
end

Apply pipeline in scope

scope "/admin", ThetaWeb do
    pipe_through [:browser, :admin]
    resources "/users", UserController    
    resources "/article", CMS.ArticleController
end

Add resource for session

defmodule ThetaWeb.SessionController do
  use ThetaWeb, :controller

  import Cap
  alias Theta.Account  
  
  ...
  
  def create(
        conn,
        %{
          "user" => %{
            "email" => email,
            "password" => password
          }
        }
      ) do
    case Account.authenticate_by_email_password(email, password) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "Welcome #{user.name}!")
        |> Cap.sign_in(user.id, user.role) # Add id and role into session           
        |> redirect(to: "/user/me")
      {:error, :unauthorized} ->
        conn
        |> put_flash(:error, "Bad email/password combination")
        |> redirect(to: Routes.session_path(conn, :new))
    end
  end

  def delete(conn, _) do
    conn
    |> configure_session(drop: true)
    |> redirect(to: "/")
  end
end

Apply ABAC in for controller:

defmodule ThetaWeb.CMS.ArticleController do
  use ThetaWeb, :controller
  
  alias Theta.CMS
  
  ...
  
  # id in param of func :show, :edit, :update, :delete
  def abac(id) do
      article = CMS.get_article!(id)
      article.author_id # compare with id in Cap sign_in
  end

end

Implement Plug.Exception for Cap.ErrorHandler

defimpl Plug.Exception, for: Cap.ErrorHandler do
  def status(exception) do
    case Integer.parse(exception.message) do
      :error -> 404
      {int, _} -> 403
    end
  end
end