{"id":13507459,"url":"https://github.com/jfrolich/authorize","last_synced_at":"2025-04-03T02:11:00.547Z","repository":{"id":41366139,"uuid":"68940403","full_name":"jfrolich/authorize","owner":"jfrolich","description":"Rule based authorization for Elixir","archived":false,"fork":false,"pushed_at":"2019-10-27T03:28:51.000Z","size":28,"stargazers_count":100,"open_issues_count":2,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-26T02:27:59.489Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jfrolich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-22T16:26:31.000Z","updated_at":"2025-02-06T00:38:23.000Z","dependencies_parsed_at":"2022-09-05T11:41:37.182Z","dependency_job_id":null,"html_url":"https://github.com/jfrolich/authorize","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jfrolich%2Fauthorize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jfrolich%2Fauthorize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jfrolich%2Fauthorize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jfrolich%2Fauthorize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jfrolich","download_url":"https://codeload.github.com/jfrolich/authorize/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246922247,"owners_count":20855345,"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":[],"created_at":"2024-08-01T02:00:34.156Z","updated_at":"2025-04-03T02:11:00.525Z","avatar_url":"https://github.com/jfrolich.png","language":"Elixir","funding_links":[],"categories":["Authorization"],"sub_categories":[],"readme":"![authorize-logo](https://user-images.githubusercontent.com/579279/39227502-8e7fbf56-488b-11e8-9711-5973fe1ba3aa.png)\n# Authorize\n\nAuthorize is a rule based authorization module for your elixir app.\n\nAuthorize walks through rules in your resource to determine if it grants authorization, or not. These rules are easily created using a DSL.\n\nAny rule can return three states:\n\n- `:ok`: grand access (return `:ok`)\n- `:next`: got to next rule\n- `:error`: return `{:error, description}` \n\nHow this translates to code is as follows:\n\n```elixir\ndefmodule Item do\n  use Authorize\n\n  defstruct user_id: nil, private?: false, invisible?: false\n\n  authorize do\n    # signature of rule:\n    # rule(\n    #  actions: one or a list of actions [optional], if not included this rule\n    #           applies to all actions\n    #  description: a description of the the rule, this will be returned as the\n    #               'reason'.\n    #  struct_or_changeset: the struct or changeset that you wish to apply the\n    #                       rule to.\n    #  actor: a data structure that describes the actor of the action. This will\n    #         be the user in most cases.\n    # )\n\n    rule \"authorize super admins for everything\", _, actor do\n      if actor.super_admin?, do: :ok, else: :next\n    end\n\n    # An :error response will stop the chain, as will an :ok response.\n    # When returning :next it will evaluate the next rule.\n    rule [:read], \"only admins can read invisible items\", struct_or_changeset, actor do\n      item = get_struct(struct_or_changeset)\n      cond do\n        item.invisible? and actor.admin? -\u003e :ok\n        item.invisible? -\u003e :error\n        :else -\u003e :next\n      end\n    end\n\n    # Action can be a list of actions, a single action such as ':read',\n    # or be completely omitted (equivalent to :all)\n    rule :read, \"actors can read their own private items\", struct_or_changeset, actor do\n      item = get_struct(struct_or_changeset)\n      if item.private? and item.user_id == actor.id do\n        :ok\n      else\n        :next\n      end\n    end\n\n    rule [:read], \"admins can read private items\", struct_or_changeset, actor do\n      if actor.admin? and get_struct(struct_or_changeset).private?, do: :ok, else: :next\n    end\n\n    rule [:read], \"all actors can read public items\", struct_or_changeset, actor do\n      if get_struct(struct_or_changeset).public?, do: :ok\n    end\n  end\nend\n\ndefmodule User do\n  defstruct id: nil, name: nil, admin?: false, super_admin?: false\nend\n```\n\nWe can now use this authorization module in the following way, with ordered rules (executed from top to bottom):\n```elixir\niex\u003e normal_user = %User{id: 1, name: \"Ed\", admin?: false}\n...\u003e admin = %User{id: 2, name: \"Admin\", admin?: true}\n...\u003e invisible_item = %Item{private?: true, invisible?: true, user_id: 2}\n...\u003e private_item = %Item{private?: true, user_id: 2}\n\niex\u003e Item.authorize(invisible_item, normal_user, :read)\n{:error, \"only admins can read invisible items\"}\n\niex\u003e Item.authorize(invisible_item, admin, :read)\n:ok\n\niex\u003e Item.authorize(private_item, normal_user, :read)\n{:error, \"no authorization rule found\"}\n\niex\u003e Item.authorize(private_item, admin, :read, include_reason: true)\n{:ok, \"members can read their own private items\"}\n```\n\nYou can define a rule with `rule [action], description, struct_or_changeset, actor`\n\nWith `rule` you are defining a rule.\n\nThe first argument is the action this rule applies to. I would recommend to use the well known CRUD (`create`, `read`, `update`, and `delete`) actions, but you can also use something else (Authorize does not care). If you leave the first argument out and start with the description, the rule will apply to all actions.\n\nThe second argument is a description, when this rule returns :error this will be passed as the reason.\n\n`struct_or_changeset` and `actor` are the variables that you can use in the rule's body. The `struct_or_changeset` is the resource, and `actor` is the actor that tries to perform the action. This can be anything you like. To make it work well with ecto we provide two helper methods `is_changeset?/1` and `get_struct/1`. `is_changeset/1` will return true if `struct_or_changeset` is a changeset. `get_struct/1` returns the struct. If the item is a changeset, it will return `changeset.data`.\n\nIf there is no rule found that returns `:error` or `:ok`, the `authorize/3` function will return `{:error, \"no authorization rule found\"}`\n\nMore examples in `test/authorize_test`.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed as:\n\n  1. Add `authorize` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:authorize, \"~\u003e 1.0.0\"}]\nend\n```\n\n  2. Ensure `authorize` is started before your application:\n\n```elixir\ndef application do\n  [applications: [:authorize]]\nend\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjfrolich%2Fauthorize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjfrolich%2Fauthorize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjfrolich%2Fauthorize/lists"}