npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@northscaler/rbac

v1.0.0-pre.4

Published

Role-based access control for arbitrary securables

Downloads

6

Readme

rbac

A role-based access control supporting library

This library provides a means to declare policies governing access control to arbitrary securables based on roles and interrogate those policies to make access control decisions.

NOTE: In the context of this discussion & this module, the term "role" refers to a type, not an instance.

Security concepts

In any security decision, there are four fundamental things required:

  • Securable: the thing access to which is being governed.
  • Principal: the thing that is attempting to perform an Action on a Securable. This is sometimes also known as a "subject".
  • Action: the activity that is being attempted.
  • Access Control Entry: the thing that binds Securable, Principals and Actions together along with an access control strategy that determines whether the activity is allowed to take place. Access Control Entrys are also sometimes known as "permissions", but that implies a positive sense that breaks down when the security implementation supports explicit denials. Acess Control Entrys are often collected into access control lists (or ACLs). Common access control strategies are "permit" & "deny", but can also be algorithmic depending on context, like "permit only if it's sunny".

Mapping of security concepts to this library

In the context of this library, a Principal is represented by a named role as a string.

The base class in this library, AbstractRoleBasedAccessControlRepository, supports arbitrary Securables, but also provides a concrete implementation, MethodAccessControlRepository, that secures methods on JavaScript classes; the Securables for that implementation are, then, the methods themselves.

The notion of Action has not yet been made explicit in this library. In the case of MethodAccessControlRepository, the Action is to simply invoke the method. The library authors anticipate adding support for Actions in a future release.

The notion of an access control list is manifested as a security policy that you declare and pass to a repository's constructor. Each policy is a JavaScript Array of entries of literal Objects. Each entry in the policy is an Access Control Entry. Each entry can have a roles property and/or a strategy property, but it must have some representation of the Securable whose access control is being declared. Since roles in this implementation are Strings, the type of an entry's roles is a RegExp that is matched to role strings. An entry's strategy can be

  • the JavaScript literal true, to indicate that access is permitted,
  • the JavaScript literal false, to indicate that access is explicitly denied, or
  • a Function that takes contextual information and returns a Boolean indicating whether or not access is permitted.

In the absence of the roles property, the value is assumed to be all roles, or the regular expression /^.+$/.

In the absence of the strategy property, this library makes the conservative choice to deny, meaning the default strategy is false.

Each concrete implementation of a repository determines what property or properties comprise the identification of the Securable. In the case of MethodAccessControlRepository, the Securable is a JavaScript class method, so the repository expects two properties, classes & methods, both of which are expected to be RegExps and are matched against the values given in the repository's interrogation methods.

Capabilities

Each repository, once given a policy, allows the consumer to interrogate whether a role is allowed to access a Securable. The interrogative methods are as follows.

  • permits({role, securable, data}): returns true if the given role (which can also be an array of roles) is (are) allowed to access the securable given optional, arbitrary, contextual data, else returns false.
  • explicitlyDenies({role, securable, data}): returns true if the given role (or roles) is (are) explicitly denied the ability to access the securable given optional, arbitrary, contextual data, else returns false.

Note the subtle distinction between the two. Users of this library will almost always use permits; expicitlyDenies is intended for more subtle use cases.

Example

Let's take MethodAccessControlRepository as an example.

NOTE: this is a low-tech example. For a high-tech usage of this library that leverages JavaScript decorators, see the test should work as intended in an app in src/test/MethodAccessControlRepository.spec.js.

First, declare your policy. We want Administrators to be able to do anything, Tellers & Managers to be able to open Accounts, but only Managers can close high-value Accounts.

// in file security.js

module.exports = [{
  // Administrators can do anything
  roles: /^Administrator$/, // role name regex
  strategy: true,           // true means "permit", false means "explicitly deny"
  classes: /^.+$/,          // regex of class names
  methods: /^.+$/           // regex of method names
}, {
  // Tellers & Managers can open accounts
  roles: /^Teller|Manager$/,
  strategy: true,
  classes: /^Account$/,
  methods: /^open$/
}, {
  // Only Managers can close high-value accounts
  strategy: ({role, account}) => { // custom access control strategy function
    switch (role) {
      case 'Teller': return account.balance < 10000
      case 'Manager': return true
      default: return false
    }
  },
  classes: /^Account$/,
  methods: /^close$/
}]

Next, instantiate your repository with the policy.

// in file rbac.js

const { MethodAccessControlRepository } = require('@northscaler/rbac')
const acl = require('./security')

module.exports = new MethodAccessControlRepository(policy)

Now, you have a repository that you can query for access control decisions. To make use of it, you need to either manually code calls to it along your regular call paths, or use a slicker, aspect-oriented solution like JavaScript decorators to intercept method calls and make access control decisions.

Here's a simple example using manual, inline security calls.

// in file Account.js
const getCallerRoles = require('./getCallerRoles') // magic that gets the roles of the current caller
const rbac = require('./rbac') // from above

function checkSecurity({ clazz, method, data}) {
 if (!rbac.permits({role: getCallerRoles(), securable: {class: clazz, method}, data})) {
   throw new Error(`no roles among ${roles.join(',')} permitted to invoke ${clazz}.${method}`)
 }
}

class Account {
  static open({id, firstName, lastName}) {
    checkSecurity({clazz: 'Account', method: 'open'})
    return new Account({id, firstName, lastname})  
  }

  constructor({id, firstName, lastName}) {
    this.id = id
    this.firstName = firstName
    this.lastName = lastName
    this.balance = 0  
  }

  close() {
    checkSecurity({clazz: 'Account', method: 'close', data: this}) // `this` will show up as `account` in strategy above
    // ...logic to close account
  }
}