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

no-acl

v0.4.0

Published

A library for managing access control of nested object structures.

Downloads

71

Readme

NO-ACL

Nested Object - Access Control List

Disclaimer: Under construction.

Installation

Install the NO-ACL using npm:

npm install no-acl

Introduction

NO-ACL (Nested Object Access Control List) is a JavaScript library that offers a novel approach to managing access control in applications. Differing from traditional ACLs, which typically secure entire resources, NO-ACL focuses on providing detailed control at the level of individual fields within nested objects. This approach enables the definition of precise access rules, ensuring that users have the appropriate level of access to data they interact with. Designed for applications with complex data structures, NO-ACL presents a secure and flexible method for handling field-level access control, making it a valuable tool for developers seeking to enhance data security and integrity.

Features

NO-ACL offers a range of features designed for effective and efficient access control management:

  • Role-Based Access Control (RBAC): Implement access control policies based on user roles, allowing for streamlined and organized management of permissions across different levels of users.

  • Support for Nested Object Structures: Specifically tailored to handle complex nested objects, ensuring that access control can be applied even in intricate data hierarchies.

  • Flexible and Extensible Rule Definition: Create custom rules that cater to the specific needs of your application. NO-ACL's flexible system allows for the tailoring of rules to suit various scenarios and requirements.

  • Wildcard Support for Path-Based Rules: Utilize wildcards in specifying paths for access control, enabling broader and more dynamic rule application without the need for defining each path explicitly.

  • Regex-Role Evaluation: Offers advanced role evaluation capabilities using regular expressions. This feature allows for more dynamic and complex role definitions, enhancing the flexibility of access control configurations.

  • Serialization: Enables the serialization of access control lists, facilitating easy storage, transfer, and reconstruction of ACLs. This feature is particularly useful for maintaining consistent access control policies across different parts of an application or in different environments.

  • Zod Extension for 'In-Place ACL Definition': Integrates seamlessly with Zod, one of the leading validation libraries, allowing for 'in-place' access control definitions. This extension simplifies the process of defining access rules within the schema validation, streamlining the workflow and ensuring that access control is tightly coupled with data validation.

Usage

Here's a basic example of how to use the NO-ACL:

import { AccessControlList as Acl, SimpleDescriptorEnum as SDE } from 'no-acl';
import type { GenericUser as IUser } from 'no-acl';

// Define your Acl rules
const acl = Acl.FromJson({
  'user.profile.name': SDE.read,
  'user.profile.email': [{ d: SDE.read, roles: ['admin'] }, SDE.none]
});

// Create a user and data object
const user: IUser = { roles: ['user'] };
const data = {
  user: {
    profile: {
      name: 'John Doe',
      email: '[email protected]'
    }
  }
};

// Apply the Acl
const result = acl.read(data, user);

// Output depends on user roles
console.log(result);
// returns:
// {
//  user: {
//    profile: {
//      name: 'John Doe'
//    }
//   }
// }

// The email field in this case is removed from the profile object, since the user has no admin role.

Zod Usage

Init

import { z } from "zod";
import * as noacl from "no-acl";

// Important, noacl needs zod for the types, but will not import it's code.
// If you won't use zod, skip this step.
// Import zod and no-acl, then init no-acl with zod.
noacl.zInit(z);

Example

import { z } from "zod";
import * as noacl from "no-acl";
import { SimpleDescriptorEnum as SDE } from 'no-acl';

noacl.zInit(z);

const DataSchema = z.object({
  user: z.object({
    // Implicit fallback to parent.
    name: z.string(),
    email: z.string().a("@admin_read"),
    dict: z.record(z.string(), z.string()).a("@rw")
    // Implicit read-write variable
  }).assignDescriptor("@rw") // Shorthand a
// Create Acl
}).ZAcl({ // Shorthand: A
  // define reusable variables for the acl
  vars: {
    // Variable for ArrayDescriptor
    "@admin_read": [
      // SpecificDescriptor
      { 
        d: SDE.read,
        // Matching role === "admin"
        roles: ["admin"]
      },
      // SimpleDescriptor
      SDE.none
    ]
  }
})

Real-Life Example: Role-Based Access Control in an Online Learning Platform

This code illustrates how this ACL system can manage access to course data based on user roles in an online learning platform context. It demonstrates the flexibility and capability of the ACL in handling complex access control scenarios.

  • We define ACL variables for different roles (administrator, instructor).
  • We create a Zod schema for a course with specific access controls for each field.
  • We apply the ACL to the course data for different user roles and log the views that each role would have.

It get's even more interesting in scenarios, where you have big and deep settings structure and want new fields to be only usable by Admins until the feature releases. However, here's a simple example:

// Defining ACL variables for role-based access
const vars: Variables = {
  "@adminWrite": { d: SDE.write, roles: ["administrator"] },
  "@adminRead": { d: SDE.read, roles: ["administrator"] },
  "@adminRW": { d: SDE.readWrite, roles: ["administrator"] },
  "@instructorWrite": { d: SDE.write, roles: ["instructor"] },
  "@instructorRead": { d: SDE.read, roles: ["instructor"] },
};

// Function to extract roles from a user
// This is the default function, if none is specified, however it can be customized to map any user property to a role map as needed.
// For example to use groups, email-address domain, name or properties. Just map them into a role-pattern you like and setup your descriptors accordingly.
const getRoles = (user: { roles: string[] }) => user.roles;

// ACL schema for a course
const courseSchema = z.object({
  title: z.string().a(["@read", "@instructorWrite", "@adminWrite"]),
  description: z.string().a(["@read", "@instructorWrite", "@adminWrite"]),
  seats: z
    .object({
      max: z.number().int().a(["@adminRW", "@instructorRead"]),
    })
    .a(["@read", "@adminRW"]),
  students: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      grades: z.array(z.number()).a(["@instructorRead"]),
      attendance: z.number().a(["@instructorRead"]),
    })
  ).a(["@read"]),
}).A({ vars, getRoles });

// Example course data
let courseData = {
  title: "How to use no-acl?",
  description: "This course will teach you how to use the no-acl library.",
  seats: {
    max: 10,
  },
  students: [
    {
      id: "001",
      name: "Melon Eusk",
      grades: [95, 76, 87],
      attendance: 90,
    },
    {
      id: "002",
      name: "Gill Bates",
      grades: [89, 67, 91],
      attendance: 85,
    },
    {
      id: "003",
      name: "Beff Jezos",
      grades: [80, 90, 67],
      attendance: 81,
    },
  ],
};

// Example usage
const adminUser = { roles: ["administrator"] };
const instructorUser = { roles: ["instructor"] };
const studentUser = { roles: ["student"] };

// Applying ACL for different user roles
const adminView = courseSchema.acl.read(courseData, adminUser);
// const adminView = courseSchema.acl.write(courseData, adminUser);
const instructorView = courseSchema.acl.read(courseData, instructorUser);
// const instructorView = courseSchema.acl.write(courseData, instructorUser);
const studentView = courseSchema.acl.read(courseData, studentUser);
// const studentView = courseSchema.acl.write(courseData, studentUser);

// Outputs:
// admin read
{
  title: 'How to use no-acl?',
  description: 'This course will teach you, how to use the no-acl library.',
  seats: { max: 10 },
  students: [
    { id: '001', name: 'Melon Eusk' },
    { id: '002', name: 'Gill Bates' },
    { id: '003', name: 'Beff Jezos' }
  ]
}
// admin write
{
  title: 'How to use no-acl?',
  description: 'This course will teach you, how to use the no-acl library.',
  seats: { max: 10 }
}

//  instructor read
{
  title: 'How to use no-acl?',
  description: 'This course will teach you, how to use the no-acl library.',
  seats: { max: 10 },
  students: [
    { id: '001', name: 'Melon Eusk', grades: [Array], attendance: 90 },
    { id: '002', name: 'Gill Bates', grades: [Array], attendance: 85 },
    { id: '003', name: 'Beff Jezos', grades: [Array], attendance: 81 }
  ]
}
// instructor write
{
  title: 'How to use no-acl?',
  description: 'This course will teach you, how to use the no-acl library.'
}

// student read
{
  title: 'How to use no-acl?',
  description: 'This course will teach you, how to use the no-acl library.',
  students: [
    { id: '001', name: 'Melon Eusk' },
    { id: '002', name: 'Gill Bates' },
    { id: '003', name: 'Beff Jezos' }
  ]
}
// student write 
{}

Outcome

  • Instructors can manage all aspects of the course, including student performance data.
  • Students can access course materials and submit assignments but cannot see other students' data.
  • Administrators have comprehensive access for oversight and management purposes.

This expanded example demonstrates the nuanced application of ACL in an online learning platform, showcasing how different roles interact with the system and the level of access granted to each.

API Reference

AccessControlList Class

This class is the core of the module, providing functionalities for managing access control lists.

Constructor

It's private to be only accessed by the FromJson static method.

Generic Types

  • Data: Represents the data structure being controlled.
  • User: The user type with access to the data.
  • Vars: The variables used within the ACL.

AccessControlList.FromJson

  • FromJson(json: AclJson, options?: Options<Vars, User>): Creates an instance of AccessControlList from a JSON object.

Options Type

The Options type is a generic type used to configure instances of the AccessControlList class. It provides flexibility in setting up the access control logic and user-role relationships. It has the following structure:

export type Options<
  Vars extends Variables = Variables,
  User extends GenericUser = GenericUser
> = {
  vars?: Vars;
  getRoles?: (user: User) => string[];
  strict?: boolean;
};

const options: Options<MyVarsType, MyUserType> = {
  vars: {
    // Custom variables for ACL
  },
  getRoles: (user) => {
    // Custom logic to retrieve user roles
    return user.customRoleProperty;
  },
  strict: true
};

const acl = AccessControlList.FromJson(aclJson, options);

Methods

  • toString(flush: boolean): Returns a string representation of the ACL.
    • flush: Boolean to determine if variables should be evaluated.
  • toJson(flush: boolean): Returns a JSON representation of the ACL.
    • flush: Boolean to determine if variables should be evaluated.
  • read(data: Data, user: User): Filters the data based on read permissions for the user.
  • write(data: Data, user: User): Filters the data based on write permissions for the user.
  • create(data: Data, user: User): Filters the data based on create permissions for the user.
  • update(data: Data, user: User): Filters the data based on update permissions for the user.
  • delete(data: Data, user: User): Filters the data based on delete permissions for the user.
  • apply(data: Data, user: User, type: SimpleDescriptorEnum, meta: Meta): Applies the specified ACL type to the data for the given user.
    • data: The data object to modify.
    • user: The user object to determine permissions.
    • type: The type of ACL to apply (read, write, create, update, delete).
    • meta: Additional metadata for ACL application.
  • getRoles(user: User): Retrieves roles for the given user.

License

This project is licensed under the MIT License - see the LICENSE file for details.