{"id":22416098,"url":"https://github.com/appunite/conductor","last_synced_at":"2025-08-01T01:30:49.413Z","repository":{"id":46633055,"uuid":"87600453","full_name":"appunite/conductor","owner":"appunite","description":"Simple elixir package for authorization","archived":false,"fork":false,"pushed_at":"2022-10-01T00:54:12.000Z","size":50,"stargazers_count":8,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-29T12:14:27.129Z","etag":null,"topics":["authorization","elixir-lang","phoenix-framework"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appunite.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-08T01:56:24.000Z","updated_at":"2023-05-14T14:38:01.000Z","dependencies_parsed_at":"2022-08-29T15:01:58.171Z","dependency_job_id":null,"html_url":"https://github.com/appunite/conductor","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appunite%2Fconductor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appunite%2Fconductor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appunite%2Fconductor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appunite%2Fconductor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appunite","download_url":"https://codeload.github.com/appunite/conductor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228321226,"owners_count":17901604,"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","elixir-lang","phoenix-framework"],"created_at":"2024-12-05T15:14:36.941Z","updated_at":"2024-12-05T15:14:37.737Z","avatar_url":"https://github.com/appunite.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Conductor\n\n[![Build Status](https://github.com/appunite/conductor/workflows/Test/badge.svg?branch=master)](https://github.com/appunite/conductor/actions) [![Hex.pm](https://img.shields.io/hexpm/v/conductor.svg?style=flat\u0026colorB=6B4D90)](https://hex.pm/packages/conductor)\n\nSimple package for **api** authorization.\n\n## When is this package good\n\n- when you need to restrict access to most endpoints when exposing some to third party developers\n- when you don't want to spam your controllers with plugs for every action\n- when you must respond according to existing permission system (e.g. scopes in jwt)\n\n## When is this package not good\n\n- when you need authentication\n- when you need authorization for html pages\n- when you need advanced permissions management system\n\n## Installation\n\n```elixir\ndef deps do\n  [{:conductor, \"~\u003e 0.4.0\"}]\nend\n```\n\n## Conductor macro\n\nBasically, this:\n\n```elixir\ndefmodule Controller do\n  use Conductor # Important! It needs to be before use Phoenix.Controller\n  use Phoenix.Controller\n\n  def index(conn, _params),  do: #...\n\n  def show(conn, _params),   do: #...\n\n  @authorize scope: \"scope1\"\n  def create(conn, _params), do: #...\n\n  @authorize scope: {\"scope1\", \"scope2\"}\n  def update(conn, _params), do: #...\n\n  @authorize scope: \"scope2\"\n  def delete(conn, _params), do: #...\nend\n```\n\nwill be compiled to this:\n\n```elixir\ndefmodule Controller do\n  use Conductor\n  use Phoenix.Controller\n\n  plug Conductor.Plugs.Authorize, [\"scope1\"] when action in [:create]\n  plug Conductor.Plugs.Authorize, [{\"scope1\", \"scope2\"}] when action in [:update]\n  plug Conductor.Plugs.Authorize, [\"scope2\"] when action in [:delete]\n  plug Conductor.Plugs.Authorize, [] when not action in [:create, :update, :delete]\n\n  def index(conn, _params),  do: #...\n  def show(conn, _params),   do: #...\n  def create(conn, _params), do: #...\n  def update(conn, _params), do: #...\n  def delete(conn, _params), do: #...\nend\n```\n\n## Root scope\n\nYou can register scope that will have full access everywhere\n\n```elixir\n  config :conductor,\n    root_scopes: [\"admin\"]\n```\n\n## Adding scopes\n\n```elixir\n  conn |\u003e Plug.Conn.assign(:scopes, [\"scope1, scope2\"])\n```\n\n## Public access\n\n```elixir\ndefmodule Router do\n  pipeline :public do\n    plug Conductor.Plugs.SkipAuthorization\n  end\n\n  scope \"/public\", MyApp do\n    pipe_through [:public]\n\n    get \"/something\", SomethingController, :something\n  end\nend\n```\n\n## Authorization failures\n\nTo avoid confusion with random `403` response codes that come from nowhere, Conductor will raise error on authorization failure as default.\n\nThis can be changed by following config\n\n```elixir\n  config :conductor,\n    on_auth_failure: :send_resp\n```\n\n## Example\n\n```elixir\n  #config\n  config :conductor,\n    root_scopes: [\"root_scope\"],\n    on_auth_failure: :send_resp\n\n  #router\n  pipeline :public do\n    plug Conductor.Plugs.SkipAuthorization\n  end\n\n  scope \"/\", Example do\n    get \"/2\", Controller, :action2\n    get \"/3\", Controller, :action3\n    get \"/4\", Controller, :action4\n    get \"/5\", Controller, :action5\n  end\n\n  scope \"/\", Example do\n    pipe_through [:public]\n\n    get \"/1\", Controller, :action1\n  end\n\n  #conns\n  conn1 = Phoenix.ConnTest.build_conn()\n  conn2 = conn1 |\u003e Plug.Conn.assign(:scopes, [\"scope1\"])\n  conn3 = conn1 |\u003e Plug.Conn.assign(:scopes, [\"scope1\", \"scope2\"])\n  conn4 = conn1 |\u003e Plug.Conn.assign(:scopes, [\"root_scope\"])\n\n  #endpoints\n  @authorize scope: \"scope1\"\n  def action1(conn, _params), do: conn |\u003e send_resp(200, \"\")\n\n  @authorize scope: \"scope2\"\n  def action2(conn, _params), do: conn |\u003e send_resp(200, \"\")\n\n  @authorize scope: {\"scope1\", \"scope2}\n  def action3(conn, _params), do: conn |\u003e send_resp(200, \"\")\n\n  def action4(conn, _params), do: conn |\u003e send_resp(200, \"\")\n\n  @authorize scopes: [\"other\", \"unused\"]\n  def action5(conn, _params), do: conn |\u003e send_resp(200, \"\")\n```\n\n```\n    | conn1 | conn2 | conn3 | conn4\n```\n\n------- | :---: | :---: | :---: | :---: action1 | 200 | 200 | 200 | 200 action2 | 403 | 200 | 200 | 200 action3 | 403 | 403 | 200 | 200 action4 | 403 | 403 | 403 | 200 action5 | 403 | 403 | 403 | 200\n\n## Customization\n\n### Global\n\n- Failure response status code\n\n  ```elixir\n    # config/config.exs\n    config :conductor, :failure_status, 418\n  ```\n\n- Failure response body\n\n  ```elixir\n    # view\n    defmodule MyApp.Web.ExampleView do\n      use MyApp.Web, :view\n\n      def render(\"403.json\", _assigns) do\n        %{message: \"Forbidden\"}\n      end\n    end\n\n    # config/config.exs\n    config :conductor, :failure_template, {MyApp.Web.ExampleView, \"403.json\"}\n  ```\n\n## License\n\nCopyright 2017-2021 Tobiasz Małecki [tobiasz.malecki@appunite.com](mailto:tobiasz.malecki@appunite.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\n\u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappunite%2Fconductor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappunite%2Fconductor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappunite%2Fconductor/lists"}