Skip to content

Commit

Permalink
Merge pull request #3 from m-elbably/strong-type-can-method
Browse files Browse the repository at this point in the history
feat(simple-access): support rerun of promise or sync data from the can method
  • Loading branch information
m-elbably authored Feb 19, 2024
2 parents 1286972 + ff44c8a commit 3949c53
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 119 deletions.
89 changes: 53 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ Attribute-Role-Based Hybrid Access Control Library
[![Coverage Status](https://coveralls.io/repos/github/m-elbably/simple-access/badge.svg?branch=master&t=pKbxsM)](https://coveralls.io/github/m-elbably/simple-access?branch=master)
[![License](https://img.shields.io/github/license/m-elbably/simple-access.svg)](https://raw.githubusercontent.com/m-elbably/simple-access/master/LICENSE)

## Installation
`npm install simple-access --save`

> **V2.0 Breaking Changes**<br>
> The `conditions` property has been removed from the `action` object due to its side effects. Instead, you can use the `scope` property to add custom attributes and validate them using application logic.
## Installation
```bash
npm install simple-access --save
````

## Features
- Hybrid access control with the best features from RBAC & ABAC
- Ability to filter resource data based on granted permission
Expand Down Expand Up @@ -55,7 +57,9 @@ Role is the level of access given to subject (user or business entity) when this

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

![Role Structure](res/images/role.svg)
<p align="center">
<img width="600" src="res/images/role.svg" alt="Role Structure">
</p>

**Role Schema**
```json
Expand All @@ -82,7 +86,9 @@ Role is the level of access given to subject (user or business entity) when this

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

![Permission Structure](res/images/permission.svg)
<p align="center">
<img width="600" src="res/images/permission.svg" alt="Permission Structure">
</p>

**Permission Schema**
```json
Expand All @@ -106,13 +112,14 @@ This library does not handle roles storage and management (Not the library conce
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.
You can implement your own roles adapter by extending `BaseAdapter` class and implement `getRolesByName` method, considering that the `getRolesByName` can return `Array<Role>` or `Promise<Array<Role>>` and you can define the return type when creating your adapter class.


**Example:**
```typescript
import { BaseAdapter, Role, ErrorEx } from "simple-access";
export class MemoryAdapter extends BaseAdapter {
export class MemoryAdapter extends BaseAdapter<Array<Role>> {
private _roles: Array<Role>;
private _cache: { [k: string]: Role } = {};
Expand Down Expand Up @@ -148,13 +155,15 @@ export class MemoryAdapter extends BaseAdapter {
## How Simple Access Works?
<p align="center">
<img width="800" src="res/images/how-it-works.svg" alt="How Simple Access Works">
</p>
- **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](res/images/how-it-works.svg)

Let's use the following set of roles as an example:
```typescript
const roles = [
Expand Down Expand Up @@ -196,17 +205,18 @@ const roles = [
### Validating Access With Single Role
You can check access using `can` function:<br>
`can(role: Array<string> | string, action: string, resource: string): Promise<Permission>`
You can check access using `can` method:
<br>
`can(role: Array<string> | string, action: string, resource: string): Promise<Permission> | Permission`
Check subject (with "operation" role) permission to "read" the resource "order"
Check subject (with "operation" role) permission to "read" the resource "order", please note that `can` method return type depends on the return type of `getRolesByName` method in the adaptor you are using. In the following example the `getRolesByName` method in the `MemoryAdaptor` return type is `Array<Role>`
```typescript
import {SimpleAccess, MemoryAdapter} from "simple-access";
const adapter = new SimpleAdapter(roles);
const adapter = new MemoryAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);
const permission = await simpleAccess.can("operation", "read", "order");
const permission = simpleAccess.can("operation", "read", "order");
if(permission.granted) {
console.log("Permissin Granted");
}
Expand All @@ -215,22 +225,28 @@ if(permission.granted) {
The returned permission
```json
{
"granted": true,
"access": {
"roles": ["operation"],
"action": "read",
"resource": "order"
},
"grants": {
"product": {
"create": {
"name": "create",
"attributes": ["*"]
}
}
},
"attributes": ["*"],
"scope": {}
"granted": true,
"access": {
"roles": [
"operation"
],
"action": "read",
"resource": "order"
},
"grants": {
"product": {
"create": {
"name": "create",
"attributes": [
"*"
]
}
}
},
"attributes": [
"*"
],
"scope": {}
}
```
Expand Down Expand Up @@ -268,13 +284,13 @@ Example:
```typescript
import {SimpleAccess, MemoryAdapter} from "simple-access";
const adapter = new SimpleAdapter(roles);
const adapter = new MemoryAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);
const permission = await simpleAccess.can(["operation", "support"], "read", "order");
const permission = simpleAccess.can(["operation", "support"], "read", "order");
if(permission.granted) {
console.log("Permissin Granted");
console.log("Permissin Granted");
}
```
Expand All @@ -290,22 +306,23 @@ You can do this using `filter` function:<br>
```typescript
import {SimpleAccess, MemoryAdapter} from "simple-access";
const adapter = new SimpleAdapter(roles);
const adapter = new MemoryAdapter(roles);
const simpleAccess = new SimpleAccess(adapter);
const resource = {
"authorId": 1002,
"price": 75.08
};
const permission = await simpleAccess.can("operation", "read", "order");
const permission = 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
```typescript
const permission = await simpleAccess.can("operation", "read", "order");
const permission = simpleAccess.can("operation", "read", "order");
if(permission.granted) {
const filteredResource = permission.filter(resource);
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions src/adapters/baseAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { Role } from "../types";

export abstract class BaseAdapter {
/**
* Base Adapter Class
* @class
* @typeParam T - Type of getRolesByName return, can be Array<Role> | Promise<Array<Role>>
*/
export abstract class BaseAdapter<
T extends Array<Role> | Promise<Array<Role>>
> {
protected constructor(public name: string) {}

abstract getRolesByName(
names: Array<string>
): Promise<Array<Role>> | Array<Role>;
/**
* @method getRolesByName
* @template T - Type of return defined on extending the class, can be Array<Role> | Promise<Array<Role>>
* @param {Array<string>} names - Roles names
* @returns {T}
*/
abstract getRolesByName(names: Array<string>): T;
}
4 changes: 2 additions & 2 deletions src/adapters/memoryAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseAdapter } from "./baseAdapter";
import { Role, ErrorEx } from "../types";

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

Expand Down Expand Up @@ -32,7 +32,7 @@ export class MemoryAdapter extends BaseAdapter {
}

getRolesByName(names: Array<string>): Array<Role> {
const result = [];
const result: Array<Role> = [];

if (names == null) {
throw new ErrorEx(
Expand Down
Loading

0 comments on commit 3949c53

Please sign in to comment.