Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@a38/core

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@a38/core - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

.turbo/turbo-lint.log

7

CHANGELOG.md
# @a38/core
## 0.0.3
### Patch Changes
- 405a971: Add support for async assertions
- 68c334b: Improve permission serialization
## 0.0.2

@@ -4,0 +11,0 @@

30

lib/index.js

@@ -137,3 +137,3 @@ "use strict";

}
resolve(roleOrRoleId, resourceOrResourceId, privilege) {
async resolve(roleOrRoleId, resourceOrResourceId, privilege) {
const role = typeof roleOrRoleId === "string" ? new Role(roleOrRoleId) : roleOrRoleId;

@@ -145,3 +145,3 @@ const resource = typeof resourceOrResourceId === "string" ? new Resource(resourceOrResourceId) : resourceOrResourceId;

for (const rule of rules) {
if (rule.match(this, role, resource, privilege)) {
if (await rule.match(this, role, resource, privilege)) {
return rule.type;

@@ -152,7 +152,7 @@ }

}
isAllowed(roleOrRoleId, resourceOrResourceId, privilege = null) {
return this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === "allow";
async isAllowed(roleOrRoleId, resourceOrResourceId, privilege = null) {
return await this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === "allow";
}
isDenied(roleOrRoleId, resourceOrResourceId, privilege = null) {
return this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === "deny";
async isDenied(roleOrRoleId, resourceOrResourceId, privilege = null) {
return await this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === "deny";
}

@@ -169,6 +169,6 @@ };

static fromJSON(json) {
if (!json || typeof json !== "object" || !("type" in json) || json.type !== "allow" && json.type !== "deny" || !("privileges" in json) || !Array.isArray(json.privileges) && json.privileges !== null) {
if (!json || typeof json !== "object" || !("type" in json) || json.type !== "allow" && json.type !== "deny" || "privileges" in json && !Array.isArray(json.privileges) && json.privileges !== null) {
throw new Error(`Invalid serialize [Rule]: ${JSON.stringify(json)}`);
}
return new _Rule(json.type, json.privileges && new Set(json.privileges));
return new _Rule(json.type, "privileges" in json && json.privileges ? new Set(json.privileges) : null);
}

@@ -242,3 +242,7 @@ match(hrbac, role, resource, privilege) {

toJSON() {
return [...this.map.entries()].map(([resource, rrMap]) => [resource, rrMap.toJSON()]);
return [...this.map.entries()].flatMap(([role, rrMap]) => {
return [...rrMap].flatMap(([resource, rules]) => {
return rules.map((rule) => [role, resource, rule.toJSON()]);
});
});
}

@@ -250,10 +254,10 @@ importJSON(json) {

for (const entry of json) {
if (!Array.isArray(entry) || entry.length !== 2) {
if (!Array.isArray(entry) || entry.length !== 3) {
throw new Error(`Invalid serialize [RoleResourceRuleMap] entry: ${JSON.stringify(entry)}`);
}
const [role, rrMap] = entry;
if (role !== null && typeof role !== "string") {
const [role, resource, rule] = entry;
if (role !== null && typeof role !== "string" || resource !== null && typeof resource !== "string") {
throw new Error(`Invalid serialize [RoleResourceRuleMap] entry: ${JSON.stringify(entry)}`);
}
this.get(role).importJSON(rrMap);
this.get(role).add(resource, Rule.fromJSON(rule));
}

@@ -260,0 +264,0 @@ return this;

{
"name": "@a38/core",
"version": "0.0.2",
"version": "0.0.3",
"exports": {

@@ -28,5 +28,5 @@ ".": {

"@a38/typedoc": "0.0.1",
"@types/node": "20.14.9",
"typescript": "5.4.5"
"@types/node": "20.14.15",
"typescript": "5.5.4"
}
}
# @a38/core
Core of the A38 hierarchical RBAC library
## Installation
```bash
npm install @a38/core # for NPM
yarn add @a38/core # for Yarn
pnpm add @a38/core # for PNPM
```
## Usage
```typescript
import { HRBAC, PermissionManager, ResourceManager, RoleManager } from '@a38/core';
const roleManager = new RoleManager();
roleManager.setParents('guest', []); // optional
roleManager.setParents('user', ['guest']); // role 'user' extends role 'guest'
roleManager.setParents('admin', ['user']); // role 'admin' extends role 'user'
const resourceManager = new ResourceManager();
resourceManager.setParents('dashboard', []); // optional
resourceManager.setParents('login', []); // optional
resourceManager.setParents('profile', []); // optional
resourceManager.setParents('admin', []); // optional
const permissionManager = new PermissionManager();
permissionManager.allow('guest', 'dashboard'); // allow 'guest' access to 'dashboard'
permissionManager.allow('guest', 'login'); // allow 'guest' access to 'dashboard'
permissionManager.allow('user', 'profile'); // allow 'user' access to 'profile'
permissionManager.deny('user', 'login'); // deny 'user' access to login
permissionManager.allow('admin'); // allow 'admin' access to everything
permissionManager.deny('admin', 'login'); // deny 'admin' access to login
const hrbac = new HRBAC(roleManager, resourceManager, permissionManager);
hrbac.isAllowed('guest', 'dashboard'); // -> true
hrbac.isAllowed('guest', 'login'); // -> true
hrbac.isAllowed('guest', 'profile'); // -> false
hrbac.isAllowed('guest', 'admin'); // -> false
hrbac.isAllowed('user', 'login'); // -> false
hrbac.isAllowed('user', 'profile'); // -> true
hrbac.isAllowed('admin', 'login'); // -> false
hrbac.isAllowed('admin', 'profile'); // -> true
hrbac.isAllowed('admin', 'admin'); // -> true
```
## Docs
See the [documentation](https://neoskop.github.io/a38/modules/_a38_core.html)
## License
@a38/core is licensed under the MIT License, See the [LICENSE](../../LICENSE) file for more details
## Sponsoring
The project development and maintenance is sponsored by [Neoskop](https://neoskop.de).
export type SerializedChildNodes = [id: string, parents: string[]][];
export abstract class ChildNode<T> {
export abstract class ChildNode<T, S extends SerializedChildNodes> {
protected parents = new Map<string, string[]>();

@@ -55,7 +55,7 @@

toJSON(): SerializedChildNodes {
return [...this.parents.entries()];
toJSON(): S {
return [...this.parents.entries()] as S;
}
importJSON(json: SerializedChildNodes | unknown) {
importJSON(json: S | unknown) {
if (!Array.isArray(json)) {

@@ -62,0 +62,0 @@ throw new Error(`Invalid serialize [${Object.getPrototypeOf(this).constructor.name}]: ${JSON.stringify(json)}`);

@@ -78,59 +78,59 @@ import { beforeEach, describe, expect, it } from 'bun:test';

describe('permissions', () => {
it('guest', () => {
expect(hrbac.isAllowed('guest', documentA, 'read')).toBeTrue();
expect(hrbac.isDenied('guest', documentA, 'read')).toBeFalse();
expect(hrbac.isAllowed('guest', documentA, 'update')).toBeFalse();
expect(hrbac.isDenied('guest', documentA, 'update')).toBeTrue();
it('guest', async () => {
expect(await hrbac.isAllowed('guest', documentA, 'read')).toBeTrue();
expect(await hrbac.isDenied('guest', documentA, 'read')).toBeFalse();
expect(await hrbac.isAllowed('guest', documentA, 'update')).toBeFalse();
expect(await hrbac.isDenied('guest', documentA, 'update')).toBeTrue();
});
it('admin', () => {
expect(hrbac.isAllowed(admin, 'settings')).toBeTrue();
expect(hrbac.isDenied(admin, 'settings')).toBeFalse();
it('admin', async () => {
expect(await hrbac.isAllowed(admin, 'settings')).toBeTrue();
expect(await hrbac.isDenied(admin, 'settings')).toBeFalse();
});
it('user', () => {
expect(hrbac.isAllowed(user, documentA, 'read')).toBeTrue();
expect(hrbac.isDenied(user, documentA, 'read')).toBeFalse();
expect(hrbac.isAllowed(user, documentA, 'list')).toBeTrue();
expect(hrbac.isDenied(user, documentA, 'list')).toBeFalse();
expect(hrbac.isAllowed(user, documentA, 'update')).toBeFalse();
expect(hrbac.isDenied(user, documentA, 'update')).toBeTrue();
it('user', async () => {
expect(await hrbac.isAllowed(user, documentA, 'read')).toBeTrue();
expect(await hrbac.isDenied(user, documentA, 'read')).toBeFalse();
expect(await hrbac.isAllowed(user, documentA, 'list')).toBeTrue();
expect(await hrbac.isDenied(user, documentA, 'list')).toBeFalse();
expect(await hrbac.isAllowed(user, documentA, 'update')).toBeFalse();
expect(await hrbac.isDenied(user, documentA, 'update')).toBeTrue();
expect(hrbac.isAllowed(user, 'ffa')).toBeTrue();
expect(hrbac.isAllowed(userV, 'ffa')).toBeTrue();
expect(await hrbac.isAllowed(user, 'ffa')).toBeTrue();
expect(await hrbac.isAllowed(userV, 'ffa')).toBeTrue();
expect(hrbac.isAllowed(user, profileU)).toBeTrue();
expect(hrbac.isAllowed(user, profileV)).toBeFalse();
expect(await hrbac.isAllowed(user, profileU)).toBeTrue();
expect(await hrbac.isAllowed(user, profileV)).toBeFalse();
expect(hrbac.isAllowed(user, documentA)).toBeFalse();
expect(await hrbac.isAllowed(user, documentA)).toBeFalse();
});
it('editor', () => {
expect(hrbac.isAllowed(editor, documentA, 'read')).toBeTrue();
expect(hrbac.isDenied(editor, documentA, 'read')).toBeFalse();
expect(hrbac.isAllowed(editor, documentA, 'list')).toBeTrue();
expect(hrbac.isDenied(editor, documentA, 'list')).toBeFalse();
expect(hrbac.isAllowed(editor, documentA, 'update')).toBeTrue();
expect(hrbac.isDenied(editor, documentA, 'update')).toBeFalse();
expect(hrbac.isAllowed(editor, documentA, 'create')).toBeFalse();
expect(hrbac.isDenied(editor, documentA, 'create')).toBeTrue();
expect(hrbac.isAllowed(editor, documentA, 'remove')).toBeFalse();
expect(hrbac.isDenied(editor, documentA, 'remove')).toBeTrue();
it('editor', async () => {
expect(await hrbac.isAllowed(editor, documentA, 'read')).toBeTrue();
expect(await hrbac.isDenied(editor, documentA, 'read')).toBeFalse();
expect(await hrbac.isAllowed(editor, documentA, 'list')).toBeTrue();
expect(await hrbac.isDenied(editor, documentA, 'list')).toBeFalse();
expect(await hrbac.isAllowed(editor, documentA, 'update')).toBeTrue();
expect(await hrbac.isDenied(editor, documentA, 'update')).toBeFalse();
expect(await hrbac.isAllowed(editor, documentA, 'create')).toBeFalse();
expect(await hrbac.isDenied(editor, documentA, 'create')).toBeTrue();
expect(await hrbac.isAllowed(editor, documentA, 'remove')).toBeFalse();
expect(await hrbac.isDenied(editor, documentA, 'remove')).toBeTrue();
});
it('author', () => {
expect(hrbac.isAllowed(authorA, documentA, 'read')).toBeTrue();
expect(hrbac.isAllowed(authorA, documentA, 'list')).toBeTrue();
expect(hrbac.isAllowed(authorA, documentA, 'update')).toBeTrue();
expect(hrbac.isAllowed(authorA, documentA, 'create')).toBeTrue();
expect(hrbac.isAllowed(authorA, documentA, 'remove')).toBeFalse();
it('author', async () => {
expect(await hrbac.isAllowed(authorA, documentA, 'read')).toBeTrue();
expect(await hrbac.isAllowed(authorA, documentA, 'list')).toBeTrue();
expect(await hrbac.isAllowed(authorA, documentA, 'update')).toBeTrue();
expect(await hrbac.isAllowed(authorA, documentA, 'create')).toBeTrue();
expect(await hrbac.isAllowed(authorA, documentA, 'remove')).toBeFalse();
expect(hrbac.isAllowed(authorB, documentA, 'read')).toBeTrue();
expect(hrbac.isAllowed(authorB, documentA, 'list')).toBeTrue();
expect(hrbac.isAllowed(authorB, documentA, 'update')).toBeFalse();
expect(hrbac.isAllowed(authorB, documentA, 'create')).toBeTrue();
expect(hrbac.isAllowed(authorB, documentA, 'remove')).toBeFalse();
expect(await hrbac.isAllowed(authorB, documentA, 'read')).toBeTrue();
expect(await hrbac.isAllowed(authorB, documentA, 'list')).toBeTrue();
expect(await hrbac.isAllowed(authorB, documentA, 'update')).toBeFalse();
expect(await hrbac.isAllowed(authorB, documentA, 'create')).toBeTrue();
expect(await hrbac.isAllowed(authorB, documentA, 'remove')).toBeFalse();
});
});
it('should support resource inheritance', () => {
it('should support resource inheritance', async () => {
hrbac = new HRBAC(new RoleManager(), new ResourceManager(), new PermissionManager());

@@ -141,4 +141,4 @@ hrbac.getResourceManager().addParents('child', ['parent']);

expect(hrbac.isAllowed('role', 'child')).toBeTrue();
expect(await hrbac.isAllowed('role', 'child')).toBeTrue();
});
});

@@ -24,3 +24,7 @@ import type { PermissionManager, Type } from './permission-manager.js';

protected resolve(roleOrRoleId: Role | string, resourceOrResourceId: Resource | string, privilege: string | null): Type | null {
protected async resolve(
roleOrRoleId: Role | string,
resourceOrResourceId: Resource | string,
privilege: string | null
): Promise<Type | null> {
const role = typeof roleOrRoleId === 'string' ? new Role(roleOrRoleId) : roleOrRoleId;

@@ -34,3 +38,3 @@ const resource = typeof resourceOrResourceId === 'string' ? new Resource(resourceOrResourceId) : resourceOrResourceId;

for (const rule of rules) {
if (rule.match(this, role, resource, privilege)) {
if (await rule.match(this, role, resource, privilege)) {
return rule.type;

@@ -43,9 +47,17 @@ }

isAllowed(roleOrRoleId: Role | string, resourceOrResourceId: Resource | string, privilege: string | null = null): boolean {
return this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === 'allow';
async isAllowed(
roleOrRoleId: Role | string,
resourceOrResourceId: Resource | string,
privilege: string | null = null
): Promise<boolean> {
return (await this.resolve(roleOrRoleId, resourceOrResourceId, privilege)) === 'allow';
}
isDenied(roleOrRoleId: Role | string, resourceOrResourceId: Resource | string, privilege: string | null = null): boolean {
return this.resolve(roleOrRoleId, resourceOrResourceId, privilege) === 'deny';
async isDenied(
roleOrRoleId: Role | string,
resourceOrResourceId: Resource | string,
privilege: string | null = null
): Promise<boolean> {
return (await this.resolve(roleOrRoleId, resourceOrResourceId, privilege)) === 'deny';
}
}

@@ -34,6 +34,6 @@ import { beforeEach, describe, expect, it } from 'bun:test';

expect(json).toEqual([
['roleA', [['resource', [{ type: 'allow', privileges: ['privilegeA'] }]]]],
['roleB', [['resource', [{ type: 'deny', privileges: ['privilegeB', 'privilegeC'] }]]]],
['roleC', [['resource', [{ type: 'allow', privileges: null }]]]],
['roleD', [[null, [{ type: 'allow', privileges: null }]]]]
['roleA', 'resource', { type: 'allow', privileges: ['privilegeA'] }],
['roleB', 'resource', { type: 'deny', privileges: ['privilegeB', 'privilegeC'] }],
['roleC', 'resource', { type: 'allow', privileges: null }],
['roleD', null, { type: 'allow', privileges: null }]
]);

@@ -40,0 +40,0 @@ });

@@ -16,3 +16,3 @@ import type { HRBAC } from './hrbac.js';

type: Type;
privileges: string[] | null;
privileges?: string[] | null;
}

@@ -27,8 +27,7 @@

(json.type !== 'allow' && json.type !== 'deny') ||
!('privileges' in json) ||
(!Array.isArray(json.privileges) && json.privileges !== null)
('privileges' in json && !Array.isArray(json.privileges) && json.privileges !== null)
) {
throw new Error(`Invalid serialize [Rule]: ${JSON.stringify(json)}`);
}
return new Rule(json.type, json.privileges && new Set(json.privileges));
return new Rule(json.type, 'privileges' in json && json.privileges ? new Set(json.privileges as string[]) : null);
}

@@ -108,4 +107,6 @@

export type SerializedRoleResourceRuleMap = [roleId: string | null, rrmap: SerializedResourceRuleMap][];
// export type SerializedRoleResourceRuleMap = [roleId: string | null, rrmap: SerializedResourceRuleMap][];
export type SerializedPermissions = (readonly [roleId: string | null, resourceId: string | null, rule: SerializedRule])[];
export class RoleResourceRuleMap implements Iterable<[string | null, ResourceRuleMap]> {

@@ -132,7 +133,11 @@ private map = new Map<string | null, ResourceRuleMap>();

toJSON(): SerializedRoleResourceRuleMap {
return [...this.map.entries()].map(([resource, rrMap]) => [resource, rrMap.toJSON()]);
toJSON(): SerializedPermissions {
return [...this.map.entries()].flatMap(([role, rrMap]) => {
return [...rrMap].flatMap(([resource, rules]) => {
return rules.map(rule => [role, resource, rule.toJSON()] as const);
});
});
}
importJSON(json: SerializedRoleResourceRuleMap | unknown) {
importJSON(json: SerializedPermissions | unknown) {
if (!Array.isArray(json)) {

@@ -142,11 +147,11 @@ throw new Error(`Invalid serialize [RoleResourceRuleMap]: ${JSON.stringify(json)}`);

for (const entry of json) {
if (!Array.isArray(entry) || entry.length !== 2) {
if (!Array.isArray(entry) || entry.length !== 3) {
throw new Error(`Invalid serialize [RoleResourceRuleMap] entry: ${JSON.stringify(entry)}`);
}
const [role, rrMap] = entry as [unknown, unknown];
if (role !== null && typeof role !== 'string') {
const [role, resource, rule] = entry as [unknown, unknown, unknown];
if ((role !== null && typeof role !== 'string') || (resource !== null && typeof resource !== 'string')) {
throw new Error(`Invalid serialize [RoleResourceRuleMap] entry: ${JSON.stringify(entry)}`);
}
this.get(role).importJSON(rrMap);
this.get(role).add(resource, Rule.fromJSON(rule)); //importJSON(rrMap);
}

@@ -203,7 +208,7 @@ return this;

toJSON(): SerializedRoleResourceRuleMap {
toJSON(): SerializedPermissions {
return this.rrrm.toJSON();
}
importJSON(json: SerializedRoleResourceRuleMap | unknown): this {
importJSON(json: SerializedPermissions | unknown): this {
this.rrrm.importJSON(json);

@@ -210,0 +215,0 @@ return this;

import { ChildNode } from './child-node.js';
export type SerializeResources = [resource: string, parents: string[]][];
export type SerializedResources = [resource: string, parents: string[]][];
export class Resource {

@@ -12,4 +12,4 @@ constructor(public readonly resourceId: string) {}

export class ResourceManager extends ChildNode<Resource> {
export class ResourceManager extends ChildNode<Resource, SerializedResources> {
protected assertEntryId = assertResourceId;
}
import { ChildNode } from './child-node.js';
export type SerializeRoles = [role: string, parents: string[]][];
export type SerializedRoles = [role: string, parents: string[]][];
export class Role {

@@ -12,4 +12,4 @@ constructor(public readonly roleId: string) {}

export class RoleManager extends ChildNode<Role> {
export class RoleManager extends ChildNode<Role, SerializedRoles> {
protected assertEntryId = assertRoleId;
}
{
"extends": ["@a38/typedoc"],
"entryPoints": ["src/index.ts", "src/awaitable.ts"]
"entryPoints": ["src/index.ts"]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc