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

@shellscape/simple-access

v1.0.2-shellscape.v1

Published

Attribute-Role-Based Hybrid Access Control Library

Downloads

1,777

Readme

Simple Access

Attribute-Role-Based Hybrid Access Control Library

Node Version npm version Build Status Coverage Status License

Installation

npm install simple-access --save

Features

  • Hybrid access control with the best features from RBAC & ABAC
  • Ability to validate subject access to resource based on granted permission
  • Ability to filter resource data based on granted permission
  • Simplicity & flexibility

Introduction

Access control is a security technique that regulates who or what can view or use resources in a computing environment.

Access Control involves:

  • Preventing unauthorized users from gaining access to resources
  • Preventing legitimate users from accessing resources in an unauthorized manner
  • Enabling legitimate users to access resources in an authorized manner

RBAC

Role-Based Access Control is a policy-neutral access-control mechanism defined around roles and privileges assigned to a user. RBAC is an additive model, so if you have overlapping role assignments, your effective grants are the union of your role assignments.

Example: If we have an API the provides data to eCommerce application, we may have a role of manager with access to a resource product and allowed actions ["create", "read", "update"], and another role operation with access to a resource product and allowed actions ["archive"]. So Any users with both manager and operation roles will be able to ["create", "read", "update", "archive"] products.

ABAC

Attribute-Based Access Control provides access to users based on who they are rather than what they do.

Components

Subject

An entity capable of accessing resources.

Resource

An entity that contains and/or receives information to which access is controlled, like "Files", "Messages".

Role

Role is the level of access given to subject (user or business entity) when this role assigned to it, it is properly viewed as a semantic construct around which access control policy is formulated.

  • Role may grant access to one or more unique resources
  • Resource may grant access to resource with based on one or more unique actions
  • Action may have list of attributes that can be used to filter resource data
  • Action may have conditions that can be validated to access a specific resource

Subject (User or business entity) can have one or more roles assigned based on their responsibilities and qualifications.

Role Structure

Role Schema

{
    "name": "string",
    "resources": [
        {
            "name": "string",
            "actions": [
                {
                    "name": "string",
                    "attributes": ["string"],
                    "conditions": ["object"], 
                    "scope": "object"
                }
            ]
        }
    ]
}

Scope is just an object that may include any metadata to be used but applications to evaluate additional constraints on subject based on application business logic.

Permission

Permission describes the way in which a subject may access a resource

Permission Structure

Permission Schema

{
	"granted": "boolean",
	"access": {
		"roles": ["string"],
		"action": "string",
		"resource": "string"
	},
	"grants": "object",
	"attributes": ["string"],
	"conditions": ["object"],
	"scope": "object"
}

Roles Storage

This library does not handle roles storage and management (Not the library concern), It has a basic MemoryAdapter implemented by extending BaseAdapter , If you need to handle more complex scenarios you will probably need to extend BaseAdapter implementing your own storage for example using [MySQL, MongoDB, Redis]

Memory Adapter

Memory adapter is a built-in roles adapter that stores all roles data into memory with simple structure.

Implement Custom Roles Adapter

You can implement your own roles adapter by extending BaseAdapter class and implement getRolesByName function.

Example:

import { BaseAdapter, Role, ErrorEx } from "simple-access";

export class MemoryAdapter extends BaseAdapter {
    private _roles: Array<Role>;
    private _cache: { [k: string]: Role } = {};

    constructor(roles?: Array<Role>) {
        super("MemoryAdapter");
        this.addRoles(roles);
    }

    setRoles(roles: Array<Role>): void {
        this._roles = roles;
        this._cache = {};
        this._roles.forEach((role: Role) => {
            this._cache[role.name] = role;
        });
    }

    getRoles(): Array<Role> {
        return this._roles;
    }

    getRolesByName(names: Array<string>): Array<Role> {
        const result = [];
        for (let i = 0; i < names.length; i += 1) {
            if (this._cache[names[i]] != null) {
                result.push(this._cache[names[i]]);
            }
        }

        return result;
    }
}

How Simple Access Works?

  • Subject (user or business entity) assigned one or more roles
  • Subject request access to a resource
  • Simple Access check subject set of roles to validate access to provided resource and action
  • Simple Access returns permission object

How Simple Access Works

Let's use the following set of roles as an example:

const roles = [
  {
    "name": "administrator",
    "resources": [
      {"name": "product", "actions": ["*"]},
      {"name": "order", "actions": ["*"]},
      {"name": "file", "actions": ["*"]}
    ]
  },
  {
    "name": "operation",
    "resources": [
      {
        "name": "product",
        "actions": [
          {"name": "create", "attributes": ["*"]},
          {"name": "read", "attributes": ["*"]},
          {"name": "update", "attributes": ["*","!history"]},
          {
            "name": "delete",
            "attributes": ["*"],
            "conditions": [
              {
                "subject.id": {
                  "$eq": "resource.authorId"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "order",
        "actions": [
          {"name": "create", "attributes": ["*"]},
          {"name": "read", "attributes": ["*"]},
          {"name": "update", "attributes": ["*"]}
        ]
      }
    ]
  }
];

Validating Access With Single Role

You can check access using can function: can(role: Array<string> | string, action: string, resource: string): Promise<Permission>

Check subject (with "operation" role) permission to "read" the resource "order"

import {SimpleAccess, MemoryAdapter} from "simple-access";

const adapter = new SimpleAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);

const permission = await simpleAccess.can("operation", "read", "order");
if(permission.granted) {
	console.log("Permissin Granted");
}

The returned permission

{
	"granted": true,
	"access": {
		"roles": ["operation"],
		"action": "read",
		"resource": "order"
	},
	"grants": {
		"product": {
			"create": {
				"name": "create",
				"attributes": ["*"]
			}
		}
	},
	"attributes": ["*"],
	"conditions": [],
	"scope": {}
}

can function only checks if subject with assigned role operation can access the resource order through read action, It will not filter resource data or evaluate conditions within action, as these functionalities provided through other functions and needs additional information to work properly.

Validating Access with Overlapped Roles

Simple Access will merge overlapped roles before validation according to these details:

  • Resources are merged (Union). If subject assigned the previous set of roles ["administrator", "operation"] then this subject will have access to these resource ["product", "order", "file"]
  • Actions are merged (Union), and the most permissive action will override.
    • Role A with resource order and actions * will override role B with actions ["read", "create"]
    • Role A with resource order and actions ["read", "update"] and role B with actions ["create", "delete"] will be merged in one resource order with actions ["read", "update", "create", "delete"]
  • Attributes are merged, and the most permissive attributes will override.
    • Attributes ["*"] will override attributes ["name", "age", "!address"]
    • Attributes ["name", "age"] will merge with attributes ["address"] into new attributes array ["name", "age", "address"]
    • Attributes ["*", "!address"] will be merged with attributes ["age"] into new attributes array ["*", "!address"]
    • Attributes ["*", "!age"] will be merged with attributes ["*", "!image", "!address"] into new attributes array ["*"] and ["age" , "image", "address"] will be allowed because they are not negated from both sides
    • Attributes ["*", "!age"] will be merged with attributes ["image"] into new attributes array ["*", "!age"] and "image" attributes will be omitted because its by default allowed

Projected (Allowed) attributes gets merged based on a union operation Negated (Not allowed) attributes gets merged based on intersection operation

  • Conditions are merged (Union), and the most permissive conditions will override.

    • Empty conditions [] will override all other conditions, and in this case no conditions will be evaluated
    • Conditions [{"subject.id": {"$eq": "resource.userId"}}] will be merged with conditions [{"subject.age": {"$gte": 16}}] into:
    [
        {"subject.id": {"$eq": "resource.userId"}},
        {"subject.age": {"$gte": 16}}
    ]	    
  • Scopes are merged (Union), and the most permissive scope will override.

    • Empty scope {} will override all other scopes
    • Scope {"group": 123} will be merged with scope {"tenant": 321} into:
    {
        "group": 123,
        "tenant": 321
    }	    

Example:

import {SimpleAccess, MemoryAdapter} from "simple-access";

const adapter = new SimpleAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);

const permission = await simpleAccess.can(["operation", "support"], "read", "order");

if(permission.granted) {
	console.log("Permissin Granted");
}

Validating Subject Access to Resource

Simple Access can check if permission allows subject (like "user") to access resource (like "order"), role conditions will be evaluated for this check.

You can use a set of MongoDB queries in JavaScript, like following operators: $in, $nin, $exists, $gte, $gt, $lte, $lt, $eq, $ne, $mod, $all, $and, $or, $nor, $not, $size, $type, $regex, $where, $elemMatch

Simple Access is depending on Sift library to support conditions within roles, Please check its documentation to create conditions in the right syntax.

You can do this check using canSubjectAccessResource function: canSubjectAccessResource(permission: Permission, subject: Object, resource: Object): boolean

You can refer to specific attributes in subject with subject. and refer to attributes in resource resource. as a prefix, and it will be substituted with the actual values before evaluation.

Example:

{
    "subject.id": {
        "$eq": "resource.authorId"
    }
}

subject.id will be substituted with the id value from subject object, and resource.authorId will be substituted with the authorId value from resource

Usage:

import {SimpleAccess, MemoryAdapter} from "simple-access";

const adapter = new SimpleAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);
const subject = {
    "id": 1002, 
    roles: ["operation"]
};
const resource = {
    "authorId": 1002,
    "price": 75.08
};

const permission = await simpleAccess.can("operation", "read", "order");
if(permission.granted) {
    // Check if user can access specific resource (object)
    // Conditions attached to role will be evaluated
    const canRead = simpleAccess.canSubjectAccessResource(permission, subject, resource);
}

For simplicity and flexibility you can also call canSubjectAccessResource from Permission object

const permission = await simpleAccess.can("operation", "read", "order");
if(permission.granted) {
    const canRead = permission.canSubjectAccessResource(subject, resource);
}

Filtering Data

Simple Access can filter resource object data based on specific permission and return only allowed attributes.

Simple Access is depending on Floppy Filter library for complex object filtering, Please check its documentation to know how to select or negate attributes in an efficient way.

You can do this using filter function: filter(permission: Permission, data: Object): Object

Example:

import {SimpleAccess, MemoryAdapter} from "simple-access";

const adapter = new SimpleAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);
const resource = {
    "authorId": 1002,
    "price": 75.08
};

const permission = await simpleAccess.can("operation", "read", "order");
if(permission.granted) {
    const filteredResource = simpleAccess.filter(permission, resource);
}

For simplicity and flexibility you can also call filter from Permission object

const permission = await simpleAccess.can("operation", "read", "order");
if(permission.granted) {
    const filteredResource = permission.filter(resource);
}

License

MIT