Kan

GitHub

Simple functional authorization library and role managment for ruby. Inspired by transproc and dry project

Basic Usage

Register abilities

class Post::Abilities
  include Kan::Abilities

  register('read') { |_, _| true }
  register('edit') { |user, post| user.id == post.user_id }
  register('delete') { |_, _| false }
end

Also, you can register more than one ability in one place and use string or symbol keys:

class Post::AdminAbilities
  include Kan::Abilities

  register(:read, :edit, :delete) { |user, _| user.admin? }
end

class Comments::Abilities
  include Kan::Abilities

  register('read') { |_, _| true }
  register('edit') { |user, _| user.admin? }

  register(:delete) do |user, comment|
    user.id == comment.user_id && comment.created_at < Time.now + TEN_MINUTES
  end
end

Check abilities

abilities = Kan::Application.new(
  post: Post::Abilities.new,
  comment: Comments::Abilities.new
)

abilities['post.read'].call(current_user, post) # => true
abilities['post.delete'].call(current_user, post) # => false
abilities['comment.delete'].call(current_user, post) # => false

Default ability block

By default Kan use proc { true } as a default ability block:

abilities['comment.invalid'].call(current_user, post) # => true

But you can rewrite it

admin_abilities = Kan::Application.new(
  post: Post::AdminAbilities.new(default_ability_block: proc { false }),
  comment: Comments::Abilities.new,
)

admin_abilities['post.delete'].call(current_user, post)  # => false
admin_abilities['post.delete'].call(admin_user, post)    # => true
admin_abilities['post.invalid'].call(current_user, post) # => false

List of abilities

You can provide array of abilities for each scope and Kan will return true if at least one ability return true:

global_abilities = Kan::Application.new(
  post: [Post::Abilities.new, Post::AdminAbilities.new],
  comment: Comments::Abilities.new
)

global_abilities['post.edit'].call(current_user, post) # => false
global_abilities['post.edit'].call(owner_user, post)   # => true
global_abilities['post.edit'].call(admin_user, post)   # => true

Aliases

You can use strings or symbols and then use it as name of ability

class Post::Abilities
  include Kan::Abilities

  register(:edit) { |_, _| true }
  register_alias(:correct, 'edit')
end

abilities = Kan::Application.new(
  post: Post::Abilities.new
)

abilities['post.correct'].call(current_user, post) # => true

Callback

You can provide callable object (that respond to #call) that accepts ability_name and payload params to after_call_callback param of your ability:

admin_abilities = Kan::Application.new(
  post: Post::AdminAbilities.new(after_call_callback: -> (ability_name, payload) { ... }),
  comment: Comments::Abilities.new,
)

admin_abilities['post.read'].call(current_user, post) # => false

Your object will be executed after calling ability.