{"id":15667643,"url":"https://github.com/subvisual/dictator","last_synced_at":"2025-08-20T04:32:37.744Z","repository":{"id":53514333,"uuid":"241845387","full_name":"subvisual/dictator","owner":"subvisual","description":"Dictates what your users see. Plug-based authorization.","archived":false,"fork":false,"pushed_at":"2021-10-20T09:33:11.000Z","size":92,"stargazers_count":78,"open_issues_count":6,"forks_count":4,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-12-09T20:22:14.323Z","etag":null,"topics":["authorization","authorization-middleware","elixir","hacktoberfest","middleware","plug"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/subvisual.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-20T09:37:39.000Z","updated_at":"2024-04-22T08:04:03.000Z","dependencies_parsed_at":"2022-09-26T18:10:37.534Z","dependency_job_id":null,"html_url":"https://github.com/subvisual/dictator","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subvisual%2Fdictator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subvisual%2Fdictator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subvisual%2Fdictator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/subvisual%2Fdictator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/subvisual","download_url":"https://codeload.github.com/subvisual/dictator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230394228,"owners_count":18218707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","authorization-middleware","elixir","hacktoberfest","middleware","plug"],"created_at":"2024-10-03T14:04:36.526Z","updated_at":"2024-12-19T07:06:44.066Z","avatar_url":"https://github.com/subvisual.png","language":"Elixir","readme":"# Dictator\n\nDictator is a plug-based authorization mechanism.\n\nDictate what your users can access in fewer than 10 lines of code:\n\n```elixir\n# config/config.exs\nconfig :dictator, repo: Client.Repo\n\n# lib/client_web/controllers/thing_controller.ex\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator\n\n  # ...\nend\n\n# lib/client_web/policies/thing.ex\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n\n  use Dictator.Policies.BelongsTo, for: Thing\nend\n```\n\nAnd that's it! Just like that your users can edit, see and delete their own\n`Thing`s but not `Thing`s belonging to other users.\n\n---\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Custom policies](#custom-policies)\n    - [`Dictator.Policies.EctoSchema`](#dictator.policies.ectoschema)\n    - [`Dictator.Policies.BelongsTo`](#dictator.policies.belongsto)\n  - [Plug Options](#plug-options)\n    - [Limitting the actions to be authorized](#limitting-the-actions-to-be-authorized)\n    - [Overriding the policy to be used](#overriding-the-policy-to-be-used)\n    - [Overriding the current user key](#overriding-the-current-user-key)\n    - [Overriding the current user fetch strategy](#overriding-the-current-user-fetch-strategy)\n  - [Configuration Options](#configuration-options)\n    - [Setting a default repo](#setting-a-default-repo)\n    - [Setting a default user key](#setting-a-default-current-user-key)\n    - [Setting the fetch strategy](#setting-the-fetch-strategy)\n    - [Setting the unauthorized handler](#setting-the-unauthorized-handler)\n- [Contributing](#contributing)\n- [Setup](#setup)\n- [Other Projects](#other-projects)\n- [About](#about)\n\n## Installation\n\nFirst, you need to add `:dictator` to your list of dependencies on your `mix.exs`:\n\n```elixir\ndef deps do\n  [{:dictator, \"~\u003e 1.1\"}]\nend\n```\n\n## Usage\n\nFor in-depth usage, refer to [this blog post](https://subvisual.com/blog/posts/dictator-1-0-a-new-perspective/).\n\nTo authorize your users, just add in your controller:\n\n```elixir\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator\n\n  # ...\nend\n```\n\nAlternatively, you can also do it at the router level:\n\n```elixir\ndefmodule ClientWeb.Router do\n  pipeline :authorised do\n    plug Dictator\n  end\nend\n```\n\nThat plug will automatically look for a `ClientWeb.Policies.Thing` module, which\nshould `use Dictator.Policy`. It is a simple module that should implement\n`can?/3`. It receives the current user, the action it is trying to perform and a\nmap containing the `conn.params`, the resource being acccessed and any options\npassed when `plug`-ing Dictator.\n\nIn `lib/client_web/policies/thing.ex`:\n\n```elixir\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n\n  use Dictator.Policies.EctoSchema, for: Thing\n\n  # User can edit, update, delete and show their own things\n  def can?(%User{id: user_id}, action, %{resource: %Thing{user_id: user_id}})\n    when action in [:edit, :update, :delete, :show], do: true\n\n  # Any user can index, new and create things\n  def can?(_, action, _) when action in [:index, :new, :create], do: true\n\n  # Users can't do anything else (users editing, updating, deleting and showing)\n  # on things they don't own\n  def can?(_, _, _), do: false\nend\n```\n\nThis exact scenario is, in fact, so common that already comes bundled as\n`Dictator.Policies.BelongsTo`. This is equivalent to the previous definition:\n\n```elixir\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n\n  use Dictator.Policies.BelongsTo, for: Thing\nend\n```\n\n**IMPORTANT: Dictator assumes you have your current user in your\n`conn.assigns`. See our [demo app](https://github.com/subvisual/dictator_demo)\nfor an example on integrating with guardian.**\n\n---\n\n### Custom Policies\n\nDictator comes bundled with three different types of policies:\n\n- **`Dictator.Policies.EctoSchema`**: most common behaviour. When you `use` it,\n  Dictator will try to call a `load_resource/1` function by passing the HTTP\n  params. This function is overridable, along with `can?/3`\n- **`Dictator.Policies.BelongsTo`**: abstraction on top of\n  `Dictator.Policies.EctoSchema`, for the most common use case: when a user\n  wants to read and write resources they own, but read access is provided to\n  everyone else. This policy makes some assumptions regarding your\n  implementation, all of those highly customisable.\n- **`Dictator.Policy`**: most basic policy possible. `use` it if you don't want\n  to load resources from the database (e.g to check if a user has an `is_admin`\n  field set to `true`)\n\n#### Dictator.Policies.EctoSchema\n\nMost common behaviour. When you `use` it, Dictator will try to call a\n`load_resource/1` function by passing the HTTP params. This allows you to access\nthe resource in the third parameter of `can/3?`. The `load_resource/1` function\nis overridable, along with `can?/3`.\n\nTake the following example:\n\n```elixir\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n\n  use Dictator.Policies.EctoSchema, for: Thing\n\n  # User can edit, update, delete and show their own things\n  def can?(%User{id: user_id}, action, %{resource: %Thing{user_id: user_id}})\n    when action in [:edit, :update, :delete, :show], do: true\n\n  # Any user can index, new and create things\n  def can?(_, action, _) when action in [:index, :new, :create], do: true\n\n  # Users can't do anything else (users editing, updating, deleting and showing)\n  # on things they don't own\n  def can?(_, _, _), do: false\nend\n```\n\nIn the example above, Dictator takes care of loading the `Thing` resource\nthrough the HTTP params. However, you might want to customise the way the\nresource is loaded. To do that, you should override the `load_resource/1`\nfunction.\n\nAs an example:\n\n```elixir\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n\n  use Dictator.Policies.EctoSchema, for: Thing\n\n  def load_resource(%{\"owner_id\" =\u003e owner_id, \"uuid\" =\u003e uuid}) do\n    ClientWeb.Repo.get_by(Thing, owner_id: owner_id, uuid: uuid)\n  end\n\n  def can?(_, action, _) when action in [:index, :show, :new, :create], do: true\n\n  def can?(%{id: owner_id}, action, %{resource: %Thing{owner_id: owner_id}})\n    when action in [:edit, :update, :delete],\n    do: true\n\n  def can?(_user, _action, _params), do: false\nend\n```\n\nThe following custom options are available:\n\n- **`key`**: defaults to `:id`, primary key of the resource being accessed.\n- **`repo`**: overrides the repo set by the config.\n\n#### Dictator.Policies.BelongsTo\n\nPolicy definition commonly used in typical `belongs_to` associations. It is an\nabstraction on top of `Dictator.Policies.EctoSchema`.\n\nThis policy assumes the users can read (`:show`, `:index`, `:new`,\n`:create`) any information but only write (`:edit`, `:update`, `:delete`)\ntheir own.\n\nAs an example, in a typical Twitter-like application, a user `has_many`\nposts and a post `belongs_to` a user. You can define a policy to let users\nmanage their own posts but read all others by doing the following:\n\n```elixir\ndefmodule MyAppWeb.Policies.Post do\n  alias MyApp.{Post, User}\n\n  use Dictator.Policies.EctoSchema, for: Post\n\n  def can?(_, action, _) when action in [:index, :show, :new, :create], do: true\n\n  def can?(%User{id: id}, action, %{resource: %Post{user_id: id}})\n      when action in [:edit, :update, :delete],\n      do: true\n\n  def can?(_, _, _), do: false\nend\n```\n\nThis scenario is so common, it is abstracted completely through this module\nand you can simply `use Dictator.Policies.BelongsTo, for: Post` to make\nuse of it. The following example is equivalent to the previous one:\n\n```elixir\ndefmodule MyAppWeb.Policies.Post do\n  use Dictator.Policies.BelongsTo, for: MyApp.Post\nend\n```\n\nThe assumptions made are that:\n\n- your resource has a `user_id` foreign key (you can change this with the\n  `:foreign_key` option)\n- your user has an `id` primary key (you can change this with the `:owner_id`\n  option)\n\nIf your user has a `uuid` primary key and the post identifies the user through a\n`:poster_id` foreign key, you can do the following:\n\n```elixir\ndefmodule MyAppWeb.Policies.Post do\n  use Dictator.Policies.BelongsTo, for: MyApp.Post,\n    foreign_key: :poster_id, owner_id: :uuid\nend\n```\n\nThe `key` and `repo` options supported by `Dictator.Policies.EctoSchema` are\nalso supported by `Dictator.Policies.BelongsTo`.\n\n### Plug Options\n\n`plug Dictator` supports 3 options:\n\n- **only/except:** (optional) - actions subject to authorization.\n- **policy:** (optional, infers the policy) - policy to be used\n- **resource\\_key:** (optional, default: `:current_user`) - key to use in the\n  conn.assigns to load the currently logged in resource.\n\n#### Limitting the actions to be authorized\n\nIf you want to only limit authorization to a few actions you can use the `:only`\nor `:except` options when calling the plug in your controller:\n\n```elixir\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator, only: [:create, :update, :delete]\n  # plug Dictator, except: [:show, :index, :new, :edit]\n\n  # ...\nend\n```\n\nIn both cases, all other actions will not go through the authorization plug and\nthe policy will only be enforced for the `create`,`update` and `delete` actions.\n\n#### Overriding the policy to be used\n\nBy default, the plug will automatically infer the policy to be used.\n`MyWebApp.UserController` would mean a `MyWebApp.Policies.User` policy to use.\n\nHowever, by using the `:policy` option, that can be overriden\n\n```elixir\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator, policy: MyPolicy\n\n  # ...\nend\n```\n\n#### Overriding the current user key\n\nBy default, the plug will automatically search for a `current_user` in the\n`conn.assigns`. You can change this behaviour by using the `key` option\nin the `plug` call. This will override the `key` option set in `config.exs`.\n\n```elixir\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator, key: :current_organization\n\n  # ...\nend\n```\n\n#### Overriding the current user fetch strategy\n\nBy default, the plug will assume you want to search for the key set in the\nprevious option in the `conn.assigns`. However, you may have it set in the\nsession or want to use a custom strategy. You can change this behaviour by\nusing the `fetch_strategy` option in the `plug` call. This will override the\n`fetch_strategy` option set in `config.exs`.\n\nThere are two strategies available by default:\n\n- `Dictator.FetchStrategies.Assigns` - fetches the given key from `conn.assigns`\n- `Dictator.FetchStrategies.Session` - fetches the given key from the session\n\n```elixir\ndefmodule ClientWeb.ThingController do\n  use ClientWeb, :controller\n\n  plug Dictator, fetch_strategy: Dictator.FetchStrategies.Session\n\n  # ...\nend\n```\n\n### Configuration Options\n\nDictator supports three options to be placed in `config/config.exs`:\n\n- **repo** - default repo to be used by `Dictator.Policies.EctoSchema`. If not\n  set, you need to define what repo to use in the policy through the `:repo`\n  option.\n- **key** (optional, defaults to `:key`) - key to be used to find the\n  current user in `conn.assigns`.\n- **unauthorized\\_handler** (optional, default:\n  `Dictator.UnauthorizedHandlers.Default`) - module to call to handle\n  unauthorisation errors.\n\n#### Setting a default repo\n\n`Dictator.Policies.EctoSchema` requires a repo to be set to load resource from.\n\nIt is recommended that you set it in `config/config.exs`:\n\n```elixir\nconfig :dictator, repo: Client.Repo\n```\n\nIf not configured, it must be provided in each policy. The `repo` option when\n`use`-ing the policy takes precedence. So you can also set a custom repo for\ncertain resources:\n\n```elixir\ndefmodule ClientWeb.Policies.Thing do\n  alias Client.Context.Thing\n  alias Client.FunkyRepoForThings\n\n  use Dictator.Policies.BelongsTo, for: Thing, repo: FunkyRepoForThings\nend\n```\n\n#### Setting a default current user key\n\nBy default, the plug will automatically search for a `current_user` in the\n`conn.assigns`. The default value is `:current_user` but this can be overriden\nby changing the config:\n\n```elixir\nconfig :dictator, key: :current_company\n```\n\nThe value set by the `key` option when plugging Dictator overrides this one.\n\n#### Setting the fetch strategy\n\nBy default, the plug will assume you want to search for the key set in the\nprevious option in the `conn.assigns`. However, you may have it set in the\nsession or want to use a custom strategy. You can change this behaviour across\nthe whole application by setting the `fetch_strategy` key in the config.\n\nThere are two strategies available by default:\n\n- `Dictator.FetchStrategies.Assigns` - fetches the given key from `conn.assigns`\n- `Dictator.FetchStrategies.Session` - fetches the given key from the session\n\n```elixir\nconfig :dictator, fetch_strategy: Dictator.FetchStrategies.Session\n```\n\nThe value set by the `key` option when plugging Dictator overrides this one.\n\n#### Setting the unauthorized handler\n\nWhen a user does not have access to a given resource, an unauthorized handler is\ncalled. By default this is `Dictator.UnauthorizedHandlers.Default` which sends a\nsimple 401 with the body set to `\"you are not authorized to do that\"`.\n\nYou can also make use of the JSON API compatible\n`Dictator.UnauthorizedHandlers.JsonApi` or provide your own:\n\n```elixir\nconfig :dictator, unauthorized_handler: MyUnauthorizedHandler\n```\n\n## Contributing\n\nFeel free to contribute.\n\nIf you found a bug, open an issue. You can also open a PR for bugs or new\nfeatures. Your PRs will be reviewed and subject to our style guide and linters.\n\nAll contributions **must** follow the [Code of\nConduct](https://github.com/subvisual/dictator/blob/master/CODE_OF_CONDUCT.md)\nand [Subvisual's guides](https://github.com/subvisual/guides).\n\n## Setup\n\nTo clone and setup the repo:\n\n```bash\ngit clone git@github.com:subvisual/dictator.git\ncd dictator\nbin/setup\n```\n\nAnd everything should automatically be installed for you.\n\nTo run the development server:\n\n```bash\nbin/server\n```\n\n## Other projects\n\nNot your cup of tea? 🍵 Here are some other Elixir alternatives we like:\n\n- [@schrockwell/bodyguard](https://github.com/schrockwell/bodyguard)\n- [@jarednorman/canada](https://github.com/jarednorman/canada)\n- [@cpjk/canary](https://github.com/cpjk/canary)\n- [@boydm/policy_wonk](https://github.com/boydm/policy_wonk)\n\n## About\n\n`Dictator` is maintained by [Subvisual](http://subvisual.com).\n\n[\u003cimg alt=\"Subvisual logo\" src=\"https://raw.githubusercontent.com/subvisual/guides/master/github/templates/subvisual_logo_with_name.png\" width=\"350px\" /\u003e](https://subvisual.com)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubvisual%2Fdictator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubvisual%2Fdictator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubvisual%2Fdictator/lists"}