{"id":17038644,"url":"https://github.com/wbotelhos/authorizy","last_synced_at":"2025-04-12T13:52:57.776Z","repository":{"id":62553749,"uuid":"311564685","full_name":"wbotelhos/authorizy","owner":"wbotelhos","description":":lock: Ruby on Rails Authorization","archived":false,"fork":false,"pushed_at":"2025-04-02T14:10:28.000Z","size":202,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T15:21:27.639Z","etag":null,"topics":["authorization","rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wbotelhos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"wbotelhos"}},"created_at":"2020-11-10T06:18:47.000Z","updated_at":"2025-04-02T14:07:27.000Z","dependencies_parsed_at":"2025-04-02T15:20:58.785Z","dependency_job_id":"3fc32ac5-acad-41ab-9e37-eb1df96b8de5","html_url":"https://github.com/wbotelhos/authorizy","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fauthorizy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fauthorizy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fauthorizy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fauthorizy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wbotelhos","download_url":"https://codeload.github.com/wbotelhos/authorizy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248576407,"owners_count":21127390,"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","rails"],"created_at":"2024-10-14T08:57:21.866Z","updated_at":"2025-04-12T13:52:57.769Z","avatar_url":"https://github.com/wbotelhos.png","language":"Ruby","funding_links":["https://github.com/sponsors/wbotelhos"],"categories":[],"sub_categories":[],"readme":"# Authorizy\n\n[![Tests](https://github.com/wbotelhos/authorizy/workflows/Tests/badge.svg)](https://github.com/wbotelhos/authorizy/actions?query=workflow:Tests)\n[![Gem Version](https://badge.fury.io/rb/authorizy.svg)](https://badge.fury.io/rb/authorizy)\n[![Maintainability](https://api.codeclimate.com/v1/badges/22ac7790d35a7c24410e/maintainability)](https://codeclimate.com/github/wbotelhos/authorizy/maintainability)\n[![Coverage](https://codecov.io/gh/wbotelhos/authorizy/branch/main/graph/badge.svg)](https://codecov.io/gh/wbotelhos/authorizy)\n[![Sponsor](https://img.shields.io/badge/sponsor-%3C3-green)](https://github.com/sponsors/wbotelhos)\n\nA JSON based Authorization.\n\n## Install\n\nAdd the following code on your `Gemfile` and run `bundle install`:\n\n```ruby\ngem 'authorizy'\n```\n\nRun the following task to create Authorizy migration and initialize.\n\n```sh\nrails g authorizy:install\n```\n\nThen execute the migration to add the column `authorizy` to your `users` table.\n\n```sh\nrake db:migrate\n```\n\n## Usage\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  include Authorizy::Extension\nend\n```\n\nAdd the `authorizy` filter on the controller you want enables authorization.\n\n```ruby\nclass UserController \u003c ApplicationController\n  before_action :authorizy\nend\n```\n\n## JSON\n\nThe column `authorizy` is a JSON column that has a key called `permission` with a list of permissions identified by the controller and action name which the user can access.\n\n```ruby\n{\n  permissions: [\n    [users, :create],\n    [users, :update],\n  }\n}\n```\n\n## Configuration\n\nYou can change the default configuration.\n\n### Aliases\n\nAlias is an action that maps another action. We have some defaults.\n\n|Action|alias |\n|------|------|\n|create|new   |\n|edit  |update|\n|new   |create|\n|update|edit  |\n\nYou can add more alias, for example, all permissions for action `index` will allow access to action `gridy` of the same controller. So `users#index` will allow `users#gridy` too.\n\n```ruby\nAuthorizy.configure do |config|\n  config.aliases = { index: :gridy }\nend\n```\n\n### Cop\n\nSometimes we need to allow access in runtime because the permission will depend on the request data and/or some dynamic logic. For this you can create a *Cop* class, that inherits from `Authorizy::BaseCop`, to allow it based on logic. It works like a [Interceptor](https://en.wikipedia.org/wiki/Interceptor_pattern).\n\nFirst, you need to configure your cop:\n\n```ruby\nAuthorizy.configure do |config|\n  config.cop = AuthorizyCop\nend\n```\n\nNow creates the cop class. The following example will intercept all access to the controller `users_controller`:\n\n```ruby\nclass AuthorizyCop \u003c Authorizy::BaseCop\n  def users\n    return false if action           == 'create'\n    return false if controller       == 'users'\n    return true  if current_user     == User.find_by(admin: true)\n    return true  if params[:allow]   == 'true'\n    return true  if session[:logged] == 'true'\n  end\nend\n```\n\nAs you can see, you have access to a couple of variables: `action`, `controller`, `current_user`, `params`, and `session`.\nWhen you return `false`, the authorization will be denied, when you return `true` your access will be allowed.\n\nIf your controller has a namespace, just use `__` to separate the modules name:\n\n```ruby\nclass AuthorizyCop \u003c Authorizy::BaseCop\n  def admin__users\n  end\nend\n```\n\nIf you want to intercept all request as the first Authorizy check, you can override the `access?` method:\n\n```ruby\nclass AuthorizyCop \u003c Authorizy::BaseCop\n  def access?\n    return true if current_user.admin?\n  end\nend\n```\n\n### Current User\n\nBy default Authorizy fetch the current user from the variable `current_user`. You have a config, that receives the controller context, where you can change it:\n\n```ruby\nAuthorizy.configure do |config|\n  config.current_user = -\u003e (context) { context.current_person }\nend\n```\n\n### Denied\n\nWhen some access is denied, by default, Authorizy checks if it is a XHR request or not and then redirect or serializes a message with status code `403`. You can rescue it by yourself:\n\n```ruby\nconfig.denied = -\u003e(context) { context.redirect_to(subscription_path, info: 'Subscription expired!') }\n```\n\n### Dependencies\n\nYou can allow access to one or more controllers and actions based on your permissions. It'll consider not only the `action`, like [aliases](#aliases) but the controller either.\n\n```ruby\nAuthorizy.configure do |config|\n  config.dependencies = {\n    payments: {\n      index: [\n        ['system/users', :index],\n        ['system/enrollments', :index],\n      ]\n    }\n  }\nend\n```\n\nSo now if a have the permission `payments#index` I'll receive more two permissions: `users#index` and `enrollments#index`.\n\n### Field\n\nBy default the permissions are located inside the field called `authorizy` in the configured `current_user`. You can change how this field is fetched:\n\n```ruby\nAuthorizy.configure do |config|\n  @field = -\u003e(current_user) { current_user.profile.authorizy }\nend\n```\n\n### Redirect URL\n\nWhen authorization fails and the request is not a XHR request a redirect happens to `/` path. You can change it:\n\n```ruby\nAuthorizy.configure do |config|\n  config.redirect_url = -\u003e (context) { context.new_session_url }\nend\n```\n\n# Helper\n\nYou can use `authorizy?` method to check if `current_user` has access to some `controller` and `action`.\n\nUsing on controller:\n\n```ruby\nclass UserController \u003c ApplicationController\n  before_action :assign_events, if: -\u003e { authorizy?('system/events', 'index') }\n\n  def assign_events\n  end\nend\n```\n\nUsing on view:\n\n```ruby\n\u003c% if authorizy?(:users, :create) %\u003e\n  \u003ca href=\"/users/new\"\u003eNew User\u003c/a\u003e\n\u003c% end %\u003e\n```\n\nUsually, we use the helper to check DB permission, not the runtime permission using the Cop file, although you can do it. Just remember that the parameters will be related to the current page, not the action you're protecting.\n\nUsing on jBuilder view:\n\n```ruby\nif authorizy?(:users, :create)\n  link_to('Create', new_users_url)\nend\n```\n\nBut if you want to simulate the access on that resource you can manually provide the same parameters dispatched when you normally access that resource:\n\n```ruby\nif authorizy?(:users, :create, params: { role: 'admin' })\n  link_to('Create', new_users_url(role: 'admin'))\nend\n```\n\nNow you're providing the same parameters used in runtime when the user accesses the link, so now, we can check the \"future\" access and prevent or allow it before happens.\n\n# Specs\n\nTo test some routes you'll need to give or not permission to the user, for that you have two ways, where the first is the user via session:\n\n```ruby\nbefore do\n  sign_in(current_user)\n\n  session[:permissions] = [[:users, :create]]\nend\n```\n\nOr you can put the permission directly in the current user:\n\n```ruby\nbefore do\n  sign_in(current_user)\n\n  current_user.update(permissions: [[:users, :create]])\nend\n```\n\n## Checks\n\nWe have a couple of checks, here is the order:\n\n1. `Authorizy::BaseCop#access?`;\n2. `session[:permissions]`;\n3. `current_user.authorizy['permissions']`;\n4. `Authorizy::BaseCop#controller_name`;\n\n## Performance\n\nIf you have few permissions, you can save the permissions in the session and avoid hitting the database many times, but if you have a couple of them, maybe it's a good idea to save them in some place like [Redis](https://redis.io).\n\n## Management\n\nIt's a good idea you keep your permissions in the database, so the customer can change it dynamically. You can load all permissions when the user is logged in and cache it later. For cache expiration, you can trigger a refresh every time that the permissions change.\n\n## Database Structure\n\nInside the database, you can use the following relation to dynamically change your permissions:\n\n```ruby\nplans -\u003e plans_permissions \u003c- permissions\n                |\n                v\n        role_plan_permissions\n                ^\n                |\n              roles\n```\n\n## RSpec\n\nYou can test your app by passing through all Authorizy layers:\n\n```ruby\nuser = User.create!(permission: { permissions: [[:users, :create]] })\n\nexpect(user).to be_authorized(:users, :create)\n```\n\nOr make sure the user does not have access:\n\n```ruby\nuser = User.create!(permission: {})\n\nexpect(user).not_to be_authorized(:users, :create)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwbotelhos%2Fauthorizy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwbotelhos%2Fauthorizy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwbotelhos%2Fauthorizy/lists"}