arac

Async Role based Access Control

Usage no npm install needed!

<script type="module">
  import arac from 'https://cdn.skypack.dev/arac';
</script>

README

arac - Async Role Based Access Control

Maintainability Test Coverage Build Status Commitizen friendly

Installation

    # with yarn:
    yarn add arac
    # with npm:
    npm i --save arac

Usage

Import

    import {AccessControl} from 'arac';

or

    const AccessControl = require('arac').AccessControl;

Basic Usage

Define roles one by one:

    import {AccessControl} from 'arac';
    const arac = new AccessControl();
    
    arac.role('admin')          // define role
      .role('user')           // define role
      

Resources can have child resources. Inheritance is defined by path. If one ot more resources in path are not defined - it creates them automatically.

   import {AccessControl} from 'arac';
   const arac = new AccessControl();
       
   arac.resource('api')        // define resource
       .resource('api/admin')  // define child resource
       
       // doing the same as: 
       .resource('api/admin') 

Define roles, resources and static permissions one by one:

arac.[allow | deny](<role>).[create | read | update | delete](<resource>)

If one or more resources or roles were not defined before - it creates them automatically. By default permission is denied.

    import {AccessControl} from 'arac';
    const arac = new AccessControl();
    
    arac.allow('admin') // define new or modify existing role
        .create('api')  // define new or modify existing recource and define new or modify existing permission
        .read('api')    // doing the same as previous line but for read action
        .update('api')  // doing the same as previous line but for update action
        .delete('api')  // doing the same as previous line but for delete action

Check permission:

arac.can(<role>).[create | read | update | delete](<resource>) : Promise

    import {AccessControl} from 'arac';
    const arac = new AccessControl();
    
    // ... roles and permissions definition
    
    arac.can('admin').create('api')
        .then((res) => {
            // allowed
        })
        .catch((error) => {
            // denied
        })

Custom Checkers

Sometimes static checker is not enough, so you can define additional dynamic. All of the checkers works sequentially starting from static one.

    import {AccessControl} from 'arac';
    const arac = new AccessControl();
    
    arac.allow('admin').create('api', {
        'checkerName1': (action: Actions, roleName: string, resourceName: string): Promise<any> => {
            let result = false;
    
            // dynamic checker body
    
            return result ? Promise.resolve() : Promise.reject();
        },
        'checkerName2': (action: Actions, roleName: string, resourceName: string): Promise<any> => {
            let result = false;
    
            // dynamic checker body
    
            return result ? Promise.resolve() : Promise.reject();
        }
    });
    
    arac.can('admin').create('api')
        .then((res) => {
            // allowed
        })
        .catch((error) => {
            // denied
        }); 
    // static checker will resolve promise
    // but depending on result of dynamic checker(-s) Promise can be rejected
    // if one of the checkers rejects - whole check is rejected

Export

For export just call export() method:

    import {AccessControl} from 'arac';
    const arac = new AccessControl();
 
    // ... roles and permissions definition

    const dump = arac.export();

Type definition of dump:

    type TAccessControlDump = {
        role: string,
        resource: string,
        permissions: Partial<{ [key in Actions]: [boolean, ...string[]] }>
    }[];

Example of dump:

    // [ 
    //  { 
    //      'role': 'role1',    
    //      'resource': 'resource1',    
    //      'permissions': {
    //          'create': [true, 'dynamicCheckerName1'],
    //          'read': [false, 'dynamicCheckerName2'],
    //          'update': [true, 'dynamicCheckerName3'],
    //          'delete': [false, 'dynamicCheckerName4']
    //      },    
    //  },
    //  { 
    //      'role': 'role2',    
    //      'resource': 'resource2',    
    //      'permissions': {
    //          'create': [true, 'dynamicCheckerName1'],
    //          'read': [false, 'dynamicCheckerName2'],
    //          'update': [true, 'dynamicCheckerName3'],
    //          'delete': [false, 'dynamicCheckerName4']
    //      },    
    //  },
    //  ...
    // ]

Import

Type definition for import dump is the same as from export method. All roles, resources and permissions not existed in AccessControl will be defined.

For import just pass dump and dynamic checkers map in controller(dump, chekersMap) or import(dump, chekersMap) method:

    import {AccessControl} from 'arac';

    const dump = [ 
         { 
             'role': 'role1',    
             'resource': 'resource1',    
             'permissions': {
                 'create': [true, 'dynamicCheckerName1'],
                 'read': [false, 'dynamicCheckerName2'],
                 'update': [true, 'dynamicCheckerName3'],
                 'delete': [false, 'dynamicCheckerName4']
             },    
         },
         { 
             'role': 'role2',    
             'resource': 'resource2',    
             'permissions': {
                 'create': [true, 'dynamicCheckerName1'],
                 'read': [false, 'dynamicCheckerName2'],
                 'update': [true, 'dynamicCheckerName3'],
                 'delete': [false, 'dynamicCheckerName4']
             },    
         },
     ];

    const checkersMap = {
        'dynamicCheckerName1': (action, roleName, resourceName): Promise<any> => {
            // return Promise.resolve();
        },
        'dynamicCheckerName2': (action, roleName, resourceName): Promise<any> => {
            // return Promise.resolve();
        },
        'dynamicCheckerName3': (action, roleName, resourceName): Promise<any> => {
            // return Promise.resolve();
        },
        'dynamicCheckerName4': (action, roleName, resourceName): Promise<any> => {
            // return Promise.resolve();
        }
    }
    
    const arac = new AccessControl(dump, checkersMap);
    // or    
    const dump = arac.import(dump, checkersMap);