
Permissions for Phoenix: who may do what, from one rule on a post to roles each customer defines and documents shared like Google Docs.
An author edits their own posts. An org's admins manage its members, with roles the org makes for itself. A folder is shared with a person, a team, or anyone with the link, and everything inside it comes along. You write each rule once, in Elixir, and the same rule answers "may Ana edit this?" and "which documents may Ana see?", the second one in your database.
Sekisho (関所) were the checkpoints on the roads of Edo Japan, where guards checked each traveller's pass. Sekisho keeps its grants in your own database, next to your data, and needs no other service.
Install
You need Elixir 1.18+, Ecto 3.13 on PostgreSQL, and Phoenix 1.7.14 or 1.8. LiveView is optional.
With Igniter, one command adds Sekisho and sets it up. It shows each change and asks before it writes:
mix igniter.install sekisho
mix ecto.migrate
Without Igniter
Add `{:sekisho, "~> 0.1"}` to the deps in `mix.exs`, then run: ```sh mix deps.get mix sekisho.gen.migration mix ecto.migrate ``` Write your permissions module in `lib/my_app/permissions.ex`: ```elixir defmodule MyApp.Permissions do use Sekisho, repo: MyApp.Repo, pubsub: MyApp.PubSub, policies: [] @impl Sekisho def actor(%MyApp.Accounts.Scope{user: user}), do: user def actor(nil), do: nil end ``` Import `allowed?/3` in the `html_helpers` of `lib/my_app_web.ex`, for your templates: ```elixir import MyApp.Permissions, only: [allowed?: 3] ``` And add `:sekisho` to the `import_deps` of `.formatter.exs`, so policies read `role :viewer, permissions: [:read]`.Your first policy
A policy holds the rules of one schema. Each rule is a function that
takes the person asking and a permission, and returns true, false, or
a condition on the record, written with Ecto.Query.dynamic/2:
defmodule MyApp.Blog.PostPolicy do
use Sekisho.Policy, for: MyApp.Blog.Post
import Ecto.Query
alias MyApp.Accounts.User
permissions [:read, :create, :update, :delete]
@impl true
def allow(%User{admin: true}, _permission), do: true
def allow(%User{id: id}, :read), do: dynamic([p], p.published or p.author_id == ^id)
def allow(%User{id: id}, :update), do: dynamic([p], p.author_id == ^id)
def allow(%User{}, :create), do: true
def allow(nil, :read), do: dynamic([p], p.published)
@impl true
def deny(_user, :update), do: dynamic([p], p.locked)
endAdd it to the policies: of your permissions module. Then ask, with
your scope first:
def update_post(scope, post, attrs) do
with :ok <- Permissions.authorize(scope, :update, post) do
post |> Post.changeset(attrs) |> Repo.update()
end
end
def list_posts(scope) do
Post |> Permissions.where_allowed(scope, :read) |> Repo.all()
endAnd in a template:
<.link :if={allowed?(@current_scope, :update, @post)} navigate={~p"/posts/#{@post}/edit"}>
Edit
</.link>authorize/3 returns {:error, %Sekisho.Denied{}}, and
authorize!/3 raises it. Phoenix answers that with a 403.
The Quickstart goes on from here, with roles and a LiveView.
What you get
- One rule for checks and lists.
allowed?/3andwhere_allowed/3come from the same condition, so a list never shows what a check refuses. → Rules - Roles, as many kinds as you need. Fixed roles, roles that include others, and roles each org makes for itself from your permissions. → Role-based access
- Sharing. Give a role on one record to a person, a team, or anyone with a link, for good or until a date. Access comes down nested folders, to any depth. → Sharing and relations
- Rules on attributes. A plan, a status, the time, the person's own fields, and the fields of a record each person may see. → Attribute rules
- All of them at once. One app with org roles, shared projects and locked records, and how Sekisho decides. → Mixing models
- Your own tables. Roles from your memberships, relations through your joins, groups from your teams. → Your own tables
- Why, in words.
explain/3gives the path that allows a permission, for a support page or a failing test. → Testing and explanations - Phoenix. Controllers, JSON, templates, and LiveView pages that lose their access while open. → Phoenix
- Fast lists. One query, each path a set the database hashes. → How it queries
Guides
Quickstart · Rules · Role-based access · Sharing and relations · Attribute rules · Mixing models · Your own tables · Phoenix · Testing and explanations · How it queries
Development
You need PostgreSQL. The tests make their own database:
mix deps.get
mix test # DATABASE_URL, or postgres://postgres:postgres@localhost/sekisho_test
mix credo --strict
License
MIT. See LICENSE.