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

@ballistagroup/method-access-controller

v1.4.0

Published

Access controller that determines whether given roles are allowed to invoke given methods on given classes.

Downloads

5

Readme

method-access-controller

This library allows you to secure methods on classes. Its fundamental capability is to be able to answer security questions like "Can tellers close accounts?", assuming closing an account means to call the close() method on an instance of class Account. This is known as role-based access control (RBAC), more specifically, role type-based access control, where static types of roles are used to determine access, as opposed to role instances.

Concepts

The following describes the relationship between the fundamental concepts in security (principal, securable, action, and access control entry) and their relationship to the concepts implemented in this library.

There are four elements required in the determination of access:

  • principal: The actor, user or system attempting to perform some action on a securable.
  • securable: The thing being secured.
  • action: The action being performed with respect to a securable.
  • access control entry: the binding of the principal, securable and action together along with the "permitted" (or "denied") boolean, or some other access control strategy.

An optional fifth element is contextual data at the point of access control.

Here are the mappings to those fundamental concepts in this library.

  • principal: a role name as a atring. This is also known as a scope in some security descriptions.
  • securable: a method on a class.
  • action: the particular method being invoked at the time of access determination.
  • access control entry: the security policy given to the constructor of the MethodAccessController.

MethodAccessController

This module exports a class called MethodAccessController. It takes in its constructor a SecurityPolicy, which is an array of SecurityPolicyEntry objects. A SecurityPolicyEntry is an object that looks like this:

const entry = {
  roles: /role names regex/,
  classes: /class names regex/,
  methods: /method names regex/,
  strategy: true // values are true, false, a function, or a string
}
  • If the strategy is the boolean literal true, then access is permitted.
  • If the strategy is the boolean literal false, then access is expicitly denied.
  • If the strategy is a function, then it must be of the form ({role, clazz, method, data}): boolean and will be invoked with the current role name, class name, method name and the contextual data, respectively.
  • If the strategy is a string, then it is require()ed, expected to return a function in form above, and invoked.

A single denial vetoes all permissions, and the absence of any permissions results in denial.

Usage

This class can be used standalone, but it's really intended to be used in conjunction with a decorator or an AOP implementation (shameless plug & tip-o-the-hat to @northscaler/aspectify) that can intercept method invocations with before advice. In this manner, access control decisions can be made if user information is available to the decorator or to the advice that delegates to a MethodAccessController. You might consider using @northscaler/continuation-local-storage to put said user information into such a place.

It's not really recommended, but in the absence of AOP, you'd use it in the following ways.

Static Security Policies

const context = require('...') // some means to get user from JWT or whatever

class Account {
  constructor(id, balance, /* ... */) {
    this.id = id
    this.balance = balance
    // ...
    this.controller = new MethodAccessController(require('./account-rbac-policy.json'))
  }
  
  // ...

  close() {
    // begin security check
    const roles = context.user?.roles

    if (roles && !roles.map(role => this.controller.grants({
      role,
      clazz: 'Account',
      method: 'close',
      data: this
    })).includes(true)) {
      const e = new Error('E_ACCESS_DENIED')
      e.context = { user: context.user, roles: context.user?.roles, clazz: 'Account', method: 'close'}
      throw e
    }
    // end security check

    this.open = false // or whatever
  }
}

Dynamic (or Algorithmic) Security Policies

You can use custom logic in your access control strategies. Here's one that only lets MANAGERs close big Accounts.

const strategy = it => it.role === 'MANAGER' || it.data?.balance < 10000
// the above strategy assumes data is the Account instance

class Account {
  constructor(id, balance, /* ... */) {
    this.id = id
    this.balance = balance
    // ...
    this.controller = new MethodAccessController([{
      classes: /^Account$/,
      methods: /^close$/,
      roles: /^.+$/,
      strategy
    }])
  }
  
  // ...

  close() {
    // begin security check
    const roles = context.user?.roles

    if (roles && !roles.map(role => this.controller.grants({
      role,
      clazz: 'Account',
      method: 'close',
      data: this // this allows the strategy to check the account's balance
    })).includes(true)) {
      const e = new Error('E_ACCESS_DENIED')
      e.context = { user: context.user, roles: context.user?.roles, clazz: 'Account', method: 'close'}
      throw e
    }
    // end security check

    this.open = false // or whatever
  }
}