## Narp
_(Nuclear Access Restriction Policies)_
Narp is an easy and flexible way to authorize function calls in elixir. It has partly been inspired by the pundit project and can be used with plain elixir or beneath a phoenix project.
### Features
* Authorize function calls easily
* Convention based syntax for clean and neat function definitions
* More complex definitions and centralized authorization policies possible
* Default policies, when no matching policy was found
### Installation
Add
```
{:narp, git: "https://github.com/ftaebi/narp.git"},
```
to the deps section of your projects mix file.
Alternatively you can use the available hex package by using.
```
{:narp, "~> 0.1.0"},
```
Narp can now be enabled by importing the Narp module.
_Plain Elixir:_
```
defmodule YourModule do
import Narp
...
end
```
_Phoenix:_ import in WebController module to make Narp accessible in every controller:
```
defmodule YourProject.Web do
def controller do
quote do
import Narp
...
end
end
end
```
### Guarded functions and policies
In order to guard a function Narp provides the **defg** _(define guarded)_ keyword. A guarded function would be defined like this:
```
defmodule YourProject.YourModule
defg your_function(params) do
...
end
end
```
In this case Narp will look for a policy module called **YourModulePolicy** and call a function with the same name as the guarded function: **YourModulePolicy.your_function(params)**. Guard functions in policy modules should return matchable data which you can use to implement conditional behavior in your guarded function.
_Example:_
```
defmodule YourProject.YourRessourceController do
defg show(conn, %{"user_id" => user_id}) do
:valid_user_id ->
render conn, "show.json", data: %{hello: "World"}
:invalid_user_id ->
conn
|> put_status(:forbidden)
|> put_error("Viewing or editing other users data is not allowed.")
end
end
defmodule YourProject.YourRessourcePolicy do
def show(conn, %{"user_id" => user_id}) do
if valid_user_id?(user_id) do
:valid_user_id
else
:invalid_user_id
end
end
defp valid_user_id?(user_id) do
user_id == 15
end
end
```
Sometimes you may want to use a single policy module to guard multiple functions in different modules. In this case you can tell Narp wich policy module and wich policy function to use:
```
defmodule YourProject.YourModule
defg your_function(params), with: {YourAwesomePolicyModule, :your_awesome_policy_function} do
...
end
end
```
_Example:_
```
defmodule YourProject.YourRessourceController do
defg show(conn, %{"user_id" => user_id}), with: {YourAwesomePolicyModule, :user_has_id_15} do
:valid_user_id ->
render conn, "show.json", data: %{hello: "World"}
:invalid_user_id ->
conn
|> put_status(:forbidden)
|> put_error("Viewing or editing other users data is not allowed.")
end
end
defmodule YourProject.YourOtherRessourceController do
defg get(conn, %{"user_id" => user_id}), with: {YourAwesomePolicyModule, :user_has_id_15} do
:valid_user_id ->
render conn, "get.json", data: %{hello: "World"}
:invalid_user_id ->
conn
|> put_status(:forbidden)
|> put_error("Viewing or editing other users data is not allowed.")
end
end
defmodule YourProject.YourAwesomePolicy do
def user_has_id_15(conn, %{"user_id" => user_id}) do
if valid_user_id?(user_id) do
:valid_user_id
else
:invalid_user_id
end
end
defp valid_user_id?(user_id) do
user_id == 15
end
end
```
Sometimes you may want to use one policy to guard many functions by default. In this cases you can define a default policy by using the function name **default_policy()** in the corresponding policy module. It is called, whenever no matching policy function could be found.
_Example:_
```
defmodule YourProject.YourRessourceController do
defg show(conn, %{"user_id" => user_id}) do
:ok ->
render conn, "show.json", data: %{hello: "World"}
{:error, msg} ->
conn
|> put_status(:forbidden)
|> put_error(msg)
end
end
defmodule YourProject.YourRessourcePolicy do
def default_policy(conn, %{"user_id" => user_id}) do
case user_id do
15 -> :ok
_ -> {:error, "only 15 allowed"}
end
end
end
```
### Credits
Special thanks to:
* Slawomir Dabek
* Matthias Lindhorst