From ee94482ef7c21ca2424eaa1106747074d07b385f Mon Sep 17 00:00:00 2001 From: Haithm EL-Watany Date: Wed, 14 Feb 2024 20:59:41 +0200 Subject: [PATCH 1/3] feat(types): define roles, resources, and actions types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🏷️ `common.ts`: Changed the `Role` type into a generic role `Role` where R is `[RoleNamesType, ResourceNamesType, ActionNamesType]` definitions. - 🏷️ `baseAdapter.ts`: Added type parameter `R`. - 🏷️ `simpleAccess.ts`: Added type parameter `R`. - ✅ Updated the unit test cases. --- src/adapters/baseAdapter.ts | 7 +- src/adapters/memoryAdapter.ts | 20 +- src/simpleAccess.ts | 42 +- src/types/common.ts | 17 +- src/types/permission.ts | 8 +- test/data/actions.ts | 7 + test/data/index.ts | 3 +- test/data/resources.ts | 722 ++++++++++++------------ test/data/roles.ts | 17 +- test/simpleAccess/memoryAdapter.spec.ts | 11 +- test/simpleAccess/simpleAccess.spec.ts | 22 +- 11 files changed, 455 insertions(+), 421 deletions(-) create mode 100644 test/data/actions.ts diff --git a/src/adapters/baseAdapter.ts b/src/adapters/baseAdapter.ts index 5c704c7..c746b1f 100644 --- a/src/adapters/baseAdapter.ts +++ b/src/adapters/baseAdapter.ts @@ -3,10 +3,13 @@ import { Role } from "../types"; /** * Base Adapter Class * @class + * @typeParam R = [RoleNameType, ResourceNameType, ActionNameType] + * array of role names, resources names, and actions names * @typeParam T - Type of getRolesByName return, can be Array | Promise> */ export abstract class BaseAdapter< - T extends Array | Promise> + R extends [string, string, string], + T extends Array> | Promise>> > { protected constructor(public name: string) {} @@ -16,5 +19,5 @@ export abstract class BaseAdapter< * @param {Array} names - Roles names * @returns {T} */ - abstract getRolesByName(names: Array): T; + abstract getRolesByName(names: Array): T; } diff --git a/src/adapters/memoryAdapter.ts b/src/adapters/memoryAdapter.ts index 4b62ff4..d6b3561 100644 --- a/src/adapters/memoryAdapter.ts +++ b/src/adapters/memoryAdapter.ts @@ -1,16 +1,18 @@ import { BaseAdapter } from "./baseAdapter"; import { Role, ErrorEx } from "../types"; -export class MemoryAdapter extends BaseAdapter> { - private _roles: Array; - private _cache: { [k: string]: Role } = {}; +export class MemoryAdapter< + R extends [string, string, string] +> extends BaseAdapter>> { + private _roles: Array>; + private _cache: { [k: string]: Role } = {}; - constructor(roles: Array) { + constructor(roles: Array>) { super("MemoryAdapter"); this.setRoles(roles); } - setRoles(roles: Array): void { + setRoles(roles: Array>): void { if (roles == null || !Array.isArray(roles) || roles.length === 0) { throw new ErrorEx( ErrorEx.VALIDATION_ERROR, @@ -21,18 +23,18 @@ export class MemoryAdapter extends BaseAdapter> { this._roles = roles; this._cache = {}; // Cache roles by name - this._roles.forEach((role: Role) => { + this._roles.forEach((role: Role) => { // this.validateGrant(grant, true); this._cache[role.name] = role; }); } - getRoles(): Array { + getRoles(): Array> { return this._roles; } - getRolesByName(names: Array): Array { - const result: Array = []; + getRolesByName(names: Array["name"]>): Array> { + const result: Array> = []; if (names == null) { throw new ErrorEx( diff --git a/src/simpleAccess.ts b/src/simpleAccess.ts index 8e4bcf4..d080e82 100644 --- a/src/simpleAccess.ts +++ b/src/simpleAccess.ts @@ -20,7 +20,10 @@ const ALL = "*"; * @class * @typeParam T - Type of Adapter */ -export class SimpleAccess> { +export class SimpleAccess< + R extends [string, string, string], + T extends BaseAdapter<[any, any, any], any> +> { constructor(private readonly _adapter: T) { if (this._adapter == null) { throw new ErrorEx( @@ -36,7 +39,7 @@ export class SimpleAccess> { * @returns {void} * @protected */ - protected validateRole(role: Role): void { + protected validateRole(role: Role): void { const validator: Ajv = new Ajv(); const isValid = validator.validate(roleSchema, role); @@ -56,7 +59,7 @@ export class SimpleAccess> { * @returns {void} * @protected */ - protected validatedAdapterRoles(roles: Array): void { + protected validatedAdapterRoles(roles: Array>): void { if (roles == null) { throw new ErrorEx( ErrorEx.VALIDATION_ERROR, @@ -71,7 +74,9 @@ export class SimpleAccess> { * @returns {{[p: string]: Tuple}} Object with merged resources, including internal data like attributes * @private */ - private getResources(roles: Array): { [k: string]: any } { + private getResources(roles: Array>): { + [k: string]: any; + } { const resources: { [k: string]: any } = {}; if (roles == null || roles.length === 0) { return resources; @@ -100,15 +105,15 @@ export class SimpleAccess> { } resource.actions.forEach((action) => { - const aObj = action as Action; - const currentAction: Action = { + const aObj = action as Action; + const currentAction: Action = { name: aObj.name, attributes: aObj.attributes ? Array.from(aObj.attributes) : [], scope: aObj.scope ? Object.assign(aObj.scope) : {}, }; - let cachedAction: Action; + let cachedAction: Action; // If we have all actions allowed no need to proceed if (resources[resource.name] === ALL) { @@ -232,9 +237,9 @@ export class SimpleAccess> { */ private getPermission( role: Array | string, - action: string, - resource: string, - roles: Array + action: R[2], + resource: R[1], + roles: Array> ): Permission { const roleNames = Array.isArray(role) ? role : [role]; const pInfo: PermissionOptions = { @@ -294,10 +299,10 @@ export class SimpleAccess> { * @returns {Permission | Promise} */ can( - role: Array | string, - action: string, - resource: string - ): CanReturnType> { + role: Array | R[0], + action: R[2], + resource: R[1] + ): CanReturnType> { const roleNames = Array.isArray(role) ? role : [role]; roleNames.forEach((r) => { @@ -313,11 +318,14 @@ export class SimpleAccess> { const roles = this._adapter.getRolesByName(roleNames); if (roles instanceof Promise) { - return roles.then((rolesArray: Array) => { + return roles.then((rolesArray: Array>) => { // Validate that all roles are available in roles list this.validatedAdapterRoles(rolesArray); return this.getPermission(role, action, resource, rolesArray); - }) as CanReturnType>; + }) as CanReturnType< + R, + TypeOfClassMethodReturn + >; } // Validate that all roles are available in roles list @@ -328,7 +336,7 @@ export class SimpleAccess> { action, resource, roles - ) as CanReturnType>; + ) as CanReturnType>; } /** diff --git a/src/types/common.ts b/src/types/common.ts index fa63621..78096be 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -12,6 +12,7 @@ type ArrayLengthMutationKeys = type ArrayItems> = T extends Array ? TItems : never; + export type FixedLengthArray = Pick< T, Exclude @@ -26,18 +27,18 @@ export class ErrorEx extends Error { } } -export interface Role { - name: string; - resources: Array; +export interface Role { + name: R[0]; + resources: Array>; } -export interface Resource { - name: string; - actions: Array | FixedLengthArray<["*"]>; +export interface Resource { + name: ResourceNamesT; + actions: Array> | FixedLengthArray<["*"]>; } -export interface Action { - name: string; +export interface Action { + name: ActionNamesT; attributes?: Array; scope?: Tuple; } diff --git a/src/types/permission.ts b/src/types/permission.ts index bd44819..6ac6f04 100644 --- a/src/types/permission.ts +++ b/src/types/permission.ts @@ -50,10 +50,12 @@ export class Permission { * @param {Tuple} data * @returns {any} */ - filter(data: Tuple) { + filter(data: Tuple): any { return Utils.filter(this, data); } } -export type CanReturnType | Promise>> = - T extends Array ? Permission : Promise; +export type CanReturnType< + R extends [string, string, string], + T extends Array> | Promise>> +> = T extends Array> ? Permission : Promise; diff --git a/test/data/actions.ts b/test/data/actions.ts new file mode 100644 index 0000000..6716c64 --- /dev/null +++ b/test/data/actions.ts @@ -0,0 +1,7 @@ +export enum ACTIONS { + CREATE = "create", + READ = "read", + UPDATE = "update", + DELETE = "delete", + EXPORT = "export", +} diff --git a/test/data/index.ts b/test/data/index.ts index 7a98913..e590169 100644 --- a/test/data/index.ts +++ b/test/data/index.ts @@ -1,2 +1,3 @@ -export * from "./resources"; export * from "./roles"; +export * from "./resources"; +export * from "./actions"; diff --git a/test/data/resources.ts b/test/data/resources.ts index 94bb75e..437ca01 100644 --- a/test/data/resources.ts +++ b/test/data/resources.ts @@ -1,411 +1,413 @@ -export const RESOURCES = { - CONFIGURATION: 'configuration', - FILE: 'file', - USER: 'user', - ORDER: 'order', - PRODUCT: 'product' -}; +export enum RESOURCES { + CONFIGURATION = "configuration", + FILE = "file", + USER = "user", + ORDER = "order", + PRODUCT = "product", +} export const USERS = [ { - "id": 1, - "firstName": "Benny", - "lastName": "Fowlie", - "email": "bfowlie0@sciencedirect.com", - "password": "c85c4ac157d5eef97e468defe65986aec9746b4b", - "address": { - "country": "Canada", - "city": "Gjoa Haven", - "street": "95 Golf Course Court", - "location": [ - -8.0604636, - 15.3487537 - ] + id: 1, + firstName: "Benny", + lastName: "Fowlie", + email: "bfowlie0@sciencedirect.com", + password: "c85c4ac157d5eef97e468defe65986aec9746b4b", + address: { + country: "Canada", + city: "Gjoa Haven", + street: "95 Golf Course Court", + location: [-8.0604636, 15.3487537], }, - "timezone": "America/Regina", - "isActive": true, - "updatedAt": "2022-02-07T00:31:19Z", - "createAt": "2022-02-08T11:56:02Z" - }, { - "id": 2, - "firstName": "Dari", - "lastName": "Runnalls", - "email": "drunnalls1@timesonline.co.uk", - "password": "c9e030ae28d72c4d5aa0b563c31bd682b267ec22", - "address": { - "country": "China", - "city": "Jianxin", - "street": "76333 Milwaukee Terrace", - "location": [ - 21.5500232, - 1.7488388 - ] + timezone: "America/Regina", + isActive: true, + updatedAt: "2022-02-07T00:31:19Z", + createAt: "2022-02-08T11:56:02Z", + }, + { + id: 2, + firstName: "Dari", + lastName: "Runnalls", + email: "drunnalls1@timesonline.co.uk", + password: "c9e030ae28d72c4d5aa0b563c31bd682b267ec22", + address: { + country: "China", + city: "Jianxin", + street: "76333 Milwaukee Terrace", + location: [21.5500232, 1.7488388], }, - "timezone": "Asia/Chongqing", - "isActive": false, - "updatedAt": "2022-05-25T13:53:55Z", - "createAt": "2021-11-13T20:17:17Z" + timezone: "Asia/Chongqing", + isActive: false, + updatedAt: "2022-05-25T13:53:55Z", + createAt: "2021-11-13T20:17:17Z", }, { - "id": 3, - "firstName": "Stacia", - "lastName": "Droogan", - "email": "sdroogan2@ucoz.ru", - "password": "f028eead33f27b932f98bdf457277a26c975b350", - "address": { - "country": "Peru", - "city": "Huacrapuquio", - "street": "63 Rowland Point", - "location": [ - 59.3433282, - -8.3251 - ] + id: 3, + firstName: "Stacia", + lastName: "Droogan", + email: "sdroogan2@ucoz.ru", + password: "f028eead33f27b932f98bdf457277a26c975b350", + address: { + country: "Peru", + city: "Huacrapuquio", + street: "63 Rowland Point", + location: [59.3433282, -8.3251], }, - "timezone": "America/Lima", - "isActive": false, - "updatedAt": "2022-06-26T18:36:35Z", - "createAt": "2021-12-05T12:35:34Z" - }, { - "id": 4, - "firstName": "Ruthi", - "lastName": "Venton", - "email": "rventon3@liveinternet.ru", - "password": "8e36a12e523709693f09efab2535a891d23b3c99", - "address": { - "country": "Russia", - "city": "Cheremshan", - "street": "49238 Jenifer Place", - "location": [ - -8.6470988, - 40.207475 - ] + timezone: "America/Lima", + isActive: false, + updatedAt: "2022-06-26T18:36:35Z", + createAt: "2021-12-05T12:35:34Z", + }, + { + id: 4, + firstName: "Ruthi", + lastName: "Venton", + email: "rventon3@liveinternet.ru", + password: "8e36a12e523709693f09efab2535a891d23b3c99", + address: { + country: "Russia", + city: "Cheremshan", + street: "49238 Jenifer Place", + location: [-8.6470988, 40.207475], }, - "timezone": "Europe/Moscow", - "isActive": false, - "updatedAt": "2022-06-23T17:30:54Z", - "createAt": "2022-04-22T09:40:42Z" - }, { - "id": 5, - "firstName": "Padget", - "lastName": "Bettanay", - "email": "pbettanay4@sohu.com", - "password": "1eb78f170c480eedeb2f9daf81ed68841506ef1f", - "address": { - "country": "China", - "city": "Hezhi", - "street": "1707 Rigney Drive", - "location": [ - 32.016539, - 49.62406 - ] + timezone: "Europe/Moscow", + isActive: false, + updatedAt: "2022-06-23T17:30:54Z", + createAt: "2022-04-22T09:40:42Z", + }, + { + id: 5, + firstName: "Padget", + lastName: "Bettanay", + email: "pbettanay4@sohu.com", + password: "1eb78f170c480eedeb2f9daf81ed68841506ef1f", + address: { + country: "China", + city: "Hezhi", + street: "1707 Rigney Drive", + location: [32.016539, 49.62406], }, - "timezone": "Asia/Shanghai", - "isActive": true, - "updatedAt": "2021-12-03T05:23:00Z", - "createAt": "2021-09-28T02:56:34Z" - } + timezone: "Asia/Shanghai", + isActive: true, + updatedAt: "2021-12-03T05:23:00Z", + createAt: "2021-09-28T02:56:34Z", + }, ]; export const PRODUCTS = [ { - "id": 1, - "authorId": 1, - "name": "Wine La Vielle Ferme Cote Du", - "category": "Home", - "brand": "Norwegian Cruise Line Holdings Ltd.", - "description": "eget elit sodales scelerisque mauris sit amet eros", - "sku": 2408, - "price": 36.68, - "image": "http://dummyimage.com/135x100.png/5fa2dd/ffffff", - "isActive": false, - "updatedAt": new Date("2022-01-01T23:33:15Z"), - "createdAt": new Date("2021-11-25T07:31:14Z") - }, { - "id": 2, - "authorId": 9, - "name": "Almonds Ground Blanched", - "category": "Toys", - "brand": "American International Group, Inc.", - "description": "et magnis dis parturient montes nascetur", - "sku": 7463, - "price": 14.01, - "image": "http://dummyimage.com/125x100.png/cc0000/ffffff", - "isActive": false, - "updatedAt": "2022-03-27T11:56:12Z", - "createdAt": "2021-08-27T17:21:29Z" - }, { - "id": 3, - "authorId": 8, - "name": "Iced Tea Concentrate", - "category": "Shoes", - "brand": "Amkor Technology, Inc.", - "description": "potenti cras", - "sku": 194, - "price": 82.14, - "image": "http://dummyimage.com/247x100.png/ff4444/ffffff", - "isActive": true, - "updatedAt": "2022-04-02T00:24:20Z", - "createdAt": "2021-09-12T10:34:24Z" - }, { - "id": 4, - "authorId": 6, - "name": "Vodka - Moskovskaya", - "category": "Grocery", - "brand": "Geron Corporation", - "description": "id nulla", - "sku": 7051, - "price": 62.54, - "image": "http://dummyimage.com/124x100.png/dddddd/000000", - "isActive": false, - "updatedAt": "2021-11-21T22:10:31Z", - "createdAt": "2022-02-12T03:37:25Z" - }, { - "id": 5, - "authorId": 3, - "name": "Cheese - Mascarpone", - "category": "Tools", - "brand": "Guggenheim Enhanced Equity Income Fund", - "description": "turpis nec euismod scelerisque quam turpis adipiscing lorem", - "sku": 164, - "price": 14.21, - "image": "http://dummyimage.com/196x100.png/dddddd/000000", - "isActive": true, - "updatedAt": "2022-07-31T01:24:12Z", - "createdAt": "2021-11-23T09:16:45Z" - }, { - "id": 6, - "authorId": 1, - "name": "Trueblue - Blueberry", - "category": "Games", - "brand": "BT Group plc", - "description": "volutpat in", - "sku": 4700, - "price": 53.49, - "image": "http://dummyimage.com/211x100.png/5fa2dd/ffffff", - "isActive": false, - "updatedAt": "2022-04-17T11:10:00Z", - "createdAt": "2022-08-11T08:31:15Z" - }, { - "id": 7, - "authorId": 2, - "name": "Taro Leaves", - "category": "Health", - "brand": "FuelCell Energy, Inc.", - "description": "pede malesuada in imperdiet et", - "sku": 4783, - "price": 53.37, - "image": "http://dummyimage.com/109x100.png/dddddd/000000", - "isActive": false, - "updatedAt": "2021-12-12T17:21:44Z", - "createdAt": "2022-05-17T11:15:13Z" - }, { - "id": 8, - "authorId": 8, - "name": "Pastry - Apple Muffins - Mini", - "category": "Sports", - "brand": "CBOE Holdings, Inc.", - "description": "suspendisse potenti cras in purus eu magna vulputate", - "sku": 5745, - "price": 70.11, - "image": "http://dummyimage.com/238x100.png/dddddd/000000", - "isActive": true, - "updatedAt": "2021-10-15T03:52:07Z", - "createdAt": "2021-12-20T07:07:35Z" - }, { - "id": 9, - "authorId": 6, - "name": "Lettuce - Curly Endive", - "category": "Industrial", - "brand": "Putnam Managed Municipal Income Trust", - "description": "id lobortis convallis", - "sku": 967, - "price": 2.17, - "image": "http://dummyimage.com/214x100.png/ff4444/ffffff", - "isActive": true, - "updatedAt": "2022-07-25T11:58:40Z", - "createdAt": "2021-09-07T13:37:40Z" - }, { - "id": 10, - "authorId": 2, - "name": "Bread - Pita, Mini", - "category": "Music", - "brand": "AudioCodes Ltd.", - "description": "a odio in", - "sku": 4016, - "price": 42.05, - "image": "http://dummyimage.com/122x100.png/dddddd/000000", - "isActive": false, - "updatedAt": "2022-01-11T17:07:16Z", - "createdAt": "2022-04-20T02:42:39Z" - } + id: 1, + authorId: 1, + name: "Wine La Vielle Ferme Cote Du", + category: "Home", + brand: "Norwegian Cruise Line Holdings Ltd.", + description: "eget elit sodales scelerisque mauris sit amet eros", + sku: 2408, + price: 36.68, + image: "http://dummyimage.com/135x100.png/5fa2dd/ffffff", + isActive: false, + updatedAt: new Date("2022-01-01T23:33:15Z"), + createdAt: new Date("2021-11-25T07:31:14Z"), + }, + { + id: 2, + authorId: 9, + name: "Almonds Ground Blanched", + category: "Toys", + brand: "American International Group, Inc.", + description: "et magnis dis parturient montes nascetur", + sku: 7463, + price: 14.01, + image: "http://dummyimage.com/125x100.png/cc0000/ffffff", + isActive: false, + updatedAt: "2022-03-27T11:56:12Z", + createdAt: "2021-08-27T17:21:29Z", + }, + { + id: 3, + authorId: 8, + name: "Iced Tea Concentrate", + category: "Shoes", + brand: "Amkor Technology, Inc.", + description: "potenti cras", + sku: 194, + price: 82.14, + image: "http://dummyimage.com/247x100.png/ff4444/ffffff", + isActive: true, + updatedAt: "2022-04-02T00:24:20Z", + createdAt: "2021-09-12T10:34:24Z", + }, + { + id: 4, + authorId: 6, + name: "Vodka - Moskovskaya", + category: "Grocery", + brand: "Geron Corporation", + description: "id nulla", + sku: 7051, + price: 62.54, + image: "http://dummyimage.com/124x100.png/dddddd/000000", + isActive: false, + updatedAt: "2021-11-21T22:10:31Z", + createdAt: "2022-02-12T03:37:25Z", + }, + { + id: 5, + authorId: 3, + name: "Cheese - Mascarpone", + category: "Tools", + brand: "Guggenheim Enhanced Equity Income Fund", + description: + "turpis nec euismod scelerisque quam turpis adipiscing lorem", + sku: 164, + price: 14.21, + image: "http://dummyimage.com/196x100.png/dddddd/000000", + isActive: true, + updatedAt: "2022-07-31T01:24:12Z", + createdAt: "2021-11-23T09:16:45Z", + }, + { + id: 6, + authorId: 1, + name: "Trueblue - Blueberry", + category: "Games", + brand: "BT Group plc", + description: "volutpat in", + sku: 4700, + price: 53.49, + image: "http://dummyimage.com/211x100.png/5fa2dd/ffffff", + isActive: false, + updatedAt: "2022-04-17T11:10:00Z", + createdAt: "2022-08-11T08:31:15Z", + }, + { + id: 7, + authorId: 2, + name: "Taro Leaves", + category: "Health", + brand: "FuelCell Energy, Inc.", + description: "pede malesuada in imperdiet et", + sku: 4783, + price: 53.37, + image: "http://dummyimage.com/109x100.png/dddddd/000000", + isActive: false, + updatedAt: "2021-12-12T17:21:44Z", + createdAt: "2022-05-17T11:15:13Z", + }, + { + id: 8, + authorId: 8, + name: "Pastry - Apple Muffins - Mini", + category: "Sports", + brand: "CBOE Holdings, Inc.", + description: "suspendisse potenti cras in purus eu magna vulputate", + sku: 5745, + price: 70.11, + image: "http://dummyimage.com/238x100.png/dddddd/000000", + isActive: true, + updatedAt: "2021-10-15T03:52:07Z", + createdAt: "2021-12-20T07:07:35Z", + }, + { + id: 9, + authorId: 6, + name: "Lettuce - Curly Endive", + category: "Industrial", + brand: "Putnam Managed Municipal Income Trust", + description: "id lobortis convallis", + sku: 967, + price: 2.17, + image: "http://dummyimage.com/214x100.png/ff4444/ffffff", + isActive: true, + updatedAt: "2022-07-25T11:58:40Z", + createdAt: "2021-09-07T13:37:40Z", + }, + { + id: 10, + authorId: 2, + name: "Bread - Pita, Mini", + category: "Music", + brand: "AudioCodes Ltd.", + description: "a odio in", + sku: 4016, + price: 42.05, + image: "http://dummyimage.com/122x100.png/dddddd/000000", + isActive: false, + updatedAt: "2022-01-11T17:07:16Z", + createdAt: "2022-04-20T02:42:39Z", + }, ]; export const ORDERS = [ { - "id": 1, - "userId": 3, - "number": 41474, - "status": "completed", - "items": [ + id: 1, + userId: 3, + number: 41474, + status: "completed", + items: [ { - "id": 5812, - "name": "Lecidella granulata (H. Magn.) R.C. Harris", - "count": 3, - "price": 8.54 - } + id: 5812, + name: "Lecidella granulata (H. Magn.) R.C. Harris", + count: 3, + price: 8.54, + }, ], - "delivery": { - "name": "Christy Hartland", - "phone": "363-256-3404", - "city": "Póvoa", - "street": "3531 Walton Circle" + delivery: { + name: "Christy Hartland", + phone: "363-256-3404", + city: "Póvoa", + street: "3531 Walton Circle", }, - "cost": { - "vat": 3.5868, - "price": 25.62, - "total": 29.2068 + cost: { + vat: 3.5868, + price: 25.62, + total: 29.2068, }, - "updatedAt": "2022-05-28T09:38:49Z", - "createdAt": "2022-06-04T13:13:45Z" - }, { - "id": 2, - "userId": 5, - "number": 87876, - "status": "completed", - "items": [ + updatedAt: "2022-05-28T09:38:49Z", + createdAt: "2022-06-04T13:13:45Z", + }, + { + id: 2, + userId: 5, + number: 87876, + status: "completed", + items: [ { - "id": 6247, - "name": "Ferocactus hamatacanthus (Muehlenpf.) Britton & Rose var. hamatacanthus", - "count": 5, - "price": 51.61 + id: 6247, + name: "Ferocactus hamatacanthus (Muehlenpf.) Britton & Rose var. hamatacanthus", + count: 5, + price: 51.61, }, { - "id": 2912, - "name": "Achlys DC.", - "count": 9, - "price": 11.13 - } + id: 2912, + name: "Achlys DC.", + count: 9, + price: 11.13, + }, ], - "delivery": { - "name": "Darryl Duprey", - "phone": "116-217-8261", - "city": "Sagay", - "street": "72 Prentice Drive" + delivery: { + name: "Darryl Duprey", + phone: "116-217-8261", + city: "Sagay", + street: "72 Prentice Drive", }, - "cost": { - "vat": 50.1508, - "price": 358.22, - "total": 408.3708 + cost: { + vat: 50.1508, + price: 358.22, + total: 408.3708, }, - "updatedAt": "2021-11-18T23:54:32Z", - "createdAt": "2022-07-01T19:20:47Z" - }, { - "id": 3, - "userId": 8, - "number": 83531, - "status": "pending", - "items": [ + updatedAt: "2021-11-18T23:54:32Z", + createdAt: "2022-07-01T19:20:47Z", + }, + { + id: 3, + userId: 8, + number: 83531, + status: "pending", + items: [ { - "id": 5243, - "name": "Agalinis maritima (Raf.) Raf. var. grandiflora (Benth.) Shinners", - "count": 1, - "price": 56.34 + id: 5243, + name: "Agalinis maritima (Raf.) Raf. var. grandiflora (Benth.) Shinners", + count: 1, + price: 56.34, }, { - "id": 3949, - "name": "Tiquilia nuttallii (Hook.) A.T. Richardson", - "count": 10, - "price": 30.74 + id: 3949, + name: "Tiquilia nuttallii (Hook.) A.T. Richardson", + count: 10, + price: 30.74, }, { - "id": 3004, - "name": "Packera tridenticulata (Rydb.) W.A. Weber & Á. Löve", - "count": 1, - "price": 42.08 - } + id: 3004, + name: "Packera tridenticulata (Rydb.) W.A. Weber & Á. Löve", + count: 1, + price: 42.08, + }, ], - "delivery": { - "name": "Linc Pibsworth", - "phone": "118-806-9880", - "city": "Ciudad del Este", - "street": "2 Ryan Court" + delivery: { + name: "Linc Pibsworth", + phone: "118-806-9880", + city: "Ciudad del Este", + street: "2 Ryan Court", }, - "cost": { - "vat": 56.8148, - "price": 405.82, - "total": 462.6348 + cost: { + vat: 56.8148, + price: 405.82, + total: 462.6348, }, - "updatedAt": "2021-12-23T22:14:39Z", - "createdAt": "2022-05-29T02:16:13Z" - }, { - "id": 4, - "userId": 10, - "number": 260, - "status": "delivered", - "items": [ + updatedAt: "2021-12-23T22:14:39Z", + createdAt: "2022-05-29T02:16:13Z", + }, + { + id: 4, + userId: 10, + number: 260, + status: "delivered", + items: [ { - "id": 5787, - "name": "Hyptis atrorubens Poit.", - "count": 8, - "price": 14.66 + id: 5787, + name: "Hyptis atrorubens Poit.", + count: 8, + price: 14.66, }, { - "id": 8479, - "name": "Hieracium murorum L.", - "count": 4, - "price": 46.42 + id: 8479, + name: "Hieracium murorum L.", + count: 4, + price: 46.42, }, { - "id": 7891, - "name": "Eriophorum ×porsildii Raymond", - "count": 5, - "price": 10.01 + id: 7891, + name: "Eriophorum ×porsildii Raymond", + count: 5, + price: 10.01, }, { - "id": 217, - "name": "Echinochloa elliptica Michael & Vick.", - "count": 9, - "price": 87.98 - } + id: 217, + name: "Echinochloa elliptica Michael & Vick.", + count: 9, + price: 87.98, + }, ], - "delivery": { - "name": "Norma Gristwood", - "phone": "673-813-1261", - "city": "Riyadh", - "street": "9 Lyons Terrace" + delivery: { + name: "Norma Gristwood", + phone: "673-813-1261", + city: "Riyadh", + street: "9 Lyons Terrace", }, - "cost": { - "vat": 160.2762, - "price": 1144.83, - "total": 1305.1062 + cost: { + vat: 160.2762, + price: 1144.83, + total: 1305.1062, }, - "updatedAt": "2021-11-30T15:20:32Z", - "createdAt": "2022-01-19T21:50:27Z" - }, { - "id": 5, - "userId": 2, - "number": 77595, - "status": "delivered", - "items": [ + updatedAt: "2021-11-30T15:20:32Z", + createdAt: "2022-01-19T21:50:27Z", + }, + { + id: 5, + userId: 2, + number: 77595, + status: "delivered", + items: [ { - "id": 5452, - "name": "Anacyclus clavatus (Desf.) Pers.", - "count": 5, - "price": 99.82 - } + id: 5452, + name: "Anacyclus clavatus (Desf.) Pers.", + count: 5, + price: 99.82, + }, ], - "delivery": { - "name": "Karim Swain", - "phone": "408-739-8665", - "city": "Subulussalam", - "street": "990 Little Fleur Street" + delivery: { + name: "Karim Swain", + phone: "408-739-8665", + city: "Subulussalam", + street: "990 Little Fleur Street", }, - "cost": { - "vat": 69.874, - "price": 499.1, - "total": 568.974 + cost: { + vat: 69.874, + price: 499.1, + total: 568.974, }, - "updatedAt": "2021-09-08T12:24:34Z", - "createdAt": "2021-08-16T12:46:04Z" - } + updatedAt: "2021-09-08T12:24:34Z", + createdAt: "2021-08-16T12:46:04Z", + }, ]; diff --git a/test/data/roles.ts b/test/data/roles.ts index 67da390..f1ae10f 100644 --- a/test/data/roles.ts +++ b/test/data/roles.ts @@ -1,13 +1,16 @@ import { RESOURCES } from "./resources"; -import { type Role } from "../../src"; +import { ACTIONS } from "./actions"; +import { Role } from "../../src"; -export const ROLES = { - ADMINISTRATOR: "administrator", - OPERATION: "operation", - SUPPORT: "support", -}; +export enum ROLES { + ADMINISTRATOR = "administrator", + OPERATION = "operation", + SUPPORT = "support", +} -export const Roles: Role[] = [ +export type RoleDefinition = [`${ROLES}`, `${RESOURCES}`, `${ACTIONS}`]; + +export const Roles: Role[] = [ { name: ROLES.ADMINISTRATOR, resources: [ diff --git a/test/simpleAccess/memoryAdapter.spec.ts b/test/simpleAccess/memoryAdapter.spec.ts index ee31835..c84c9ae 100644 --- a/test/simpleAccess/memoryAdapter.spec.ts +++ b/test/simpleAccess/memoryAdapter.spec.ts @@ -1,13 +1,13 @@ import { expect } from "chai"; import { before, describe, it } from "mocha"; -import { SimpleAccess, Role, MemoryAdapter, ErrorEx } from "../../src"; -import { Roles } from "../data"; +import { SimpleAccess, MemoryAdapter, ErrorEx } from "../../src"; +import { RoleDefinition, Roles } from "../data"; -let adapter: MemoryAdapter; -let acl: SimpleAccess; +let adapter: MemoryAdapter; +let acl: SimpleAccess; before(() => { - adapter = new MemoryAdapter(Roles as Role[]); + adapter = new MemoryAdapter(Roles); acl = new SimpleAccess(adapter); }); @@ -33,6 +33,7 @@ describe("Memory adapter functionalities", () => { }); it("Should return empty array because role(s) does not exists", async () => { + // @ts-ignore: Unreachable code error const result = adapter.getRolesByName(["none"]); expect(result).to.be.an("array").with.lengthOf(0); }); diff --git a/test/simpleAccess/simpleAccess.spec.ts b/test/simpleAccess/simpleAccess.spec.ts index 8165223..b6b4af8 100644 --- a/test/simpleAccess/simpleAccess.spec.ts +++ b/test/simpleAccess/simpleAccess.spec.ts @@ -1,15 +1,14 @@ import { expect } from "chai"; import { before, describe, it } from "mocha"; - -import { ErrorEx, Role, Permission } from "../../src"; -import { Roles, ROLES, RESOURCES, PRODUCTS } from "../data"; +import { ErrorEx, Permission } from "../../src"; +import { RoleDefinition, Roles, ROLES, RESOURCES, PRODUCTS } from "../data"; import { SimpleAccess, MemoryAdapter } from "../../src"; -let adapter: MemoryAdapter; -let acl: SimpleAccess; +let adapter: MemoryAdapter; +let acl: SimpleAccess; before(() => { - adapter = new MemoryAdapter(Roles as any[]); + adapter = new MemoryAdapter(Roles); acl = new SimpleAccess(adapter); }); @@ -58,6 +57,7 @@ describe("Test core functionalities", () => { it("Should return validation error, for missing role", async () => { const ROLE_NAME = "finance"; try { + // @ts-ignore: Unreachable code error acl.can(ROLE_NAME, "create", "product"); } catch (e) { expect(e) @@ -96,6 +96,7 @@ describe("Test core functionalities", () => { it("Should return validation error object, if one or more roles are missing", async () => { try { + // @ts-ignore: Unreachable code error acl.can([ROLES.ADMINISTRATOR, "auditor"], "read", "product"); } catch (e) { expect(e) @@ -156,6 +157,7 @@ describe("Test permission object", () => { describe("Test can functionality with single role", () => { it("Should return permission with granted equal false when resource does not exist", async () => { + // @ts-ignore: Unreachable code error const permission = acl.can([ROLES.OPERATION], "delete", "languages"); const { granted } = permission; expect(granted).to.be.equal(false); @@ -176,6 +178,7 @@ describe("Test can functionality with single role", () => { it("Should return permission with granted equal true when subject has access to all actions on resource", async () => { const permission = acl.can( [ROLES.ADMINISTRATOR], + // @ts-ignore: Unreachable code error "readAll", RESOURCES.FILE ); @@ -222,7 +225,7 @@ describe("Test can functionality with overlapped roles - resources", () => { it("Should return permission object with merged (union) resources", async () => { const { grants } = permission; const resources: { [k: string]: any } = {}; - const roles = acl.adapter.getRolesByName(ROLE_NAME) as Role[]; + const roles = acl.adapter.getRolesByName(ROLE_NAME); roles.forEach((role) => { role.resources.forEach((resource) => { @@ -247,7 +250,7 @@ describe("Test can functionality with overlapped roles - actions", () => { const { grants: { [RESOURCE_NAME]: gResource }, } = permission; - const roles = acl.adapter.getRolesByName(ROLE_NAME) as Role[]; + const roles = acl.adapter.getRolesByName(ROLE_NAME); const actions: { [k: string]: any } = {}; roles.forEach((role) => { @@ -285,6 +288,7 @@ describe("Test can functionality with overlapped roles - actions", () => { it("Should return permission object with the most permissive action applied and granted access to custom action", async () => { const permission = acl.can( [ROLES.ADMINISTRATOR, ROLES.OPERATION], + // @ts-ignore: Unreachable code error "print", RESOURCES.CONFIGURATION ); @@ -470,7 +474,7 @@ describe("Test can functionality with overlapped roles - scope", () => { const { grants: { [RESOURCE_NAME]: resource }, } = permission; - const roles = acl.adapter.getRolesByName(ROLE_NAME) as Role[]; + const roles = acl.adapter.getRolesByName(ROLE_NAME); const scope: any = {}; roles.forEach((role) => { From 25da7f42c2df35800c049bf27ede76c687e89aaa Mon Sep 17 00:00:00 2001 From: Haithm EL-Watany Date: Wed, 14 Feb 2024 21:27:56 +0200 Subject: [PATCH 2/3] docs(usage): update usage examples in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 📝 Updated the documentation with the new modifications and use cases. --- README.md | 319 +++++++++++++++++++++++++++++++++++++++++--- src/simpleAccess.ts | 2 +- test/data/roles.ts | 48 +++---- 3 files changed, 324 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f9b15ff..fac8d81 100644 --- a/README.md +++ b/README.md @@ -119,29 +119,48 @@ You can implement your own roles adapter by extending `BaseAdapter` class and im ```typescript import { BaseAdapter, Role, ErrorEx } from "simple-access"; -export class MemoryAdapter extends BaseAdapter> { - private _roles: Array; - private _cache: { [k: string]: Role } = {}; +export class MemoryAdapter< + R extends [string, string, string] +> extends BaseAdapter>> { + private _roles: Array>; + private _cache: { [k: string]: Role } = {}; - constructor(roles?: Array) { + constructor(roles: Array>) { super("MemoryAdapter"); - this.addRoles(roles); + this.setRoles(roles); } - setRoles(roles: Array): void { + setRoles(roles: Array>): void { + if (roles == null || !Array.isArray(roles) || roles.length === 0) { + throw new ErrorEx( + ErrorEx.VALIDATION_ERROR, + `Missing/Invalid roles array in "${this.constructor.name}"` + ); + } + this._roles = roles; this._cache = {}; - this._roles.forEach((role: Role) => { + // Cache roles by name + this._roles.forEach((role: Role) => { + // this.validateGrant(grant, true); this._cache[role.name] = role; }); } - getRoles(): Array { + getRoles(): Array> { return this._roles; } - getRolesByName(names: Array): Array { - const result = []; + getRolesByName(names: Array["name"]>): Array> { + const result: Array> = []; + + if (names == null) { + throw new ErrorEx( + ErrorEx.VALIDATION_ERROR, + `Roles names array can not be null or undefined` + ); + } + for (let i = 0; i < names.length; i += 1) { if (this._cache[names[i]] != null) { result.push(this._cache[names[i]]); @@ -166,7 +185,14 @@ export class MemoryAdapter extends BaseAdapter> { Let's use the following set of roles as an example: ```typescript -const roles = [ +import type { Role } from "simple-access"; + +type RoleNamesType = "administrator" | "operation"; +type ResourceNamesType = "product" | "order" | "file"; +type ActionNamesType = "create" | "read" | "update" | "delete" +type RoleDefinition = [RoleNamesType, ResourceNamesType, ActionNamesType] + +const roles: Role[] = [ { "name": "administrator", "resources": [ @@ -209,16 +235,101 @@ You can check access using `can` method:
`can(role: Array | string, action: string, resource: string): Promise | Permission` -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` +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` + ```typescript -import {SimpleAccess, MemoryAdapter} from "simple-access"; +import { + type Role, + SimpleAccess, + MemoryAdapter +} from "simple-access"; +import type { + RoleDefinition +} from './roles'; + +type RoleNamesType = + "administrator" + | "operation"; +type ResourceNamesType = + "product" + | "order" + | "file"; +type ActionNamesType = + "create" + | "read" + | "update" + | "delete" +type RoleDefinition = [RoleNamesType, ResourceNamesType, ActionNamesType] + +const roles: Role[] = [ + { + "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": ["*"] + } + ] + }, + { + "name": "order", + "actions": [ + { + "name": "create", + "attributes": ["*"] + }, + { + "name": "read", + "attributes": ["*"] + }, + { + "name": "update", + "attributes": ["*"] + } + ] + } + ] + } +]; const adapter = new MemoryAdapter(roles); -const simpleAccess = new SimpleAccess(adapter); +const simpleAccess = new SimpleAccess(adapter); const permission = simpleAccess.can("operation", "read", "order"); -if(permission.granted) { - console.log("Permissin Granted"); +if (permission.granted) { + console.log("Permissin Granted"); } ``` @@ -282,10 +393,94 @@ The returned permission Example: ```typescript -import {SimpleAccess, MemoryAdapter} from "simple-access"; +import { + type Role, + SimpleAccess, + MemoryAdapter +} from "simple-access"; +import type { + RoleDefinition +} from './roles'; + +type RoleNamesType = + "administrator" + | "operation"; +type ResourceNamesType = + "product" + | "order" + | "file"; +type ActionNamesType = + "create" + | "read" + | "update" + | "delete" +type RoleDefinition = [RoleNamesType, ResourceNamesType, ActionNamesType] + +const roles: Role[] = [ + { + "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": ["*"] + } + ] + }, + { + "name": "order", + "actions": [ + { + "name": "create", + "attributes": ["*"] + }, + { + "name": "read", + "attributes": ["*"] + }, + { + "name": "update", + "attributes": ["*"] + } + ] + } + ] + } +]; const adapter = new MemoryAdapter(roles); -const simpleAccess = new SimpleAccess(adapter); +const simpleAccess = new SimpleAccess(adapter); const permission = simpleAccess.can(["operation", "support"], "read", "order"); @@ -304,10 +499,94 @@ You can do this using `filter` function:
**Example:** ```typescript -import {SimpleAccess, MemoryAdapter} from "simple-access"; +import { + type Role, + SimpleAccess, + MemoryAdapter +} from "simple-access"; +import type { + RoleDefinition +} from './roles'; + +type RoleNamesType = + "administrator" + | "operation"; +type ResourceNamesType = + "product" + | "order" + | "file"; +type ActionNamesType = + "create" + | "read" + | "update" + | "delete" +type RoleDefinition = [RoleNamesType, ResourceNamesType, ActionNamesType] + +const roles: Role[] = [ + { + "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": ["*"] + } + ] + }, + { + "name": "order", + "actions": [ + { + "name": "create", + "attributes": ["*"] + }, + { + "name": "read", + "attributes": ["*"] + }, + { + "name": "update", + "attributes": ["*"] + } + ] + } + ] + } +]; const adapter = new MemoryAdapter(roles); -const simpleAccess = new SimpleAccess(adapter); +const simpleAccess = new SimpleAccess(adapter); const resource = { "authorId": 1002, "price": 75.08 diff --git a/src/simpleAccess.ts b/src/simpleAccess.ts index d080e82..174f13e 100644 --- a/src/simpleAccess.ts +++ b/src/simpleAccess.ts @@ -22,7 +22,7 @@ const ALL = "*"; */ export class SimpleAccess< R extends [string, string, string], - T extends BaseAdapter<[any, any, any], any> + T extends BaseAdapter > { constructor(private readonly _adapter: T) { if (this._adapter == null) { diff --git a/test/data/roles.ts b/test/data/roles.ts index f1ae10f..6c21d79 100644 --- a/test/data/roles.ts +++ b/test/data/roles.ts @@ -25,26 +25,26 @@ export const Roles: Role[] = [ { name: RESOURCES.USER, actions: [ - { name: "create", attributes: ["*"] }, - { name: "read", attributes: ["*"] }, - { name: "update", attributes: ["*"] }, + { name: ACTIONS.CREATE, attributes: ["*"] }, + { name: ACTIONS.READ, attributes: ["*"] }, + { name: ACTIONS.UPDATE, attributes: ["*"] }, ], }, { name: RESOURCES.PRODUCT, actions: [ - { name: "create", attributes: ["*", "active"] }, - { name: "read", attributes: ["active"] }, - { name: "update", attributes: ["*", "!history"] }, - { name: "delete", attributes: ["*"] }, + { name: ACTIONS.CREATE, attributes: ["*", "active"] }, + { name: ACTIONS.READ, attributes: ["active"] }, + { name: ACTIONS.UPDATE, attributes: ["*", "!history"] }, + { name: ACTIONS.DELETE, attributes: ["*"] }, ], }, { name: RESOURCES.ORDER, actions: [ - { name: "create", attributes: ["*"] }, - { name: "read", attributes: ["*"] }, - { name: "update", attributes: ["*"] }, + { name: ACTIONS.CREATE, attributes: ["*"] }, + { name: ACTIONS.READ, attributes: ["*"] }, + { name: ACTIONS.UPDATE, attributes: ["*"] }, ], }, ], @@ -56,22 +56,22 @@ export const Roles: Role[] = [ name: RESOURCES.CONFIGURATION, actions: [ { - name: "read", + name: ACTIONS.READ, attributes: ["*"], }, - { name: "update", attributes: ["status", "items"] }, + { name: ACTIONS.UPDATE, attributes: ["status", "items"] }, ], }, { name: RESOURCES.ORDER, actions: [ { - name: "read", + name: ACTIONS.READ, attributes: ["*"], }, - { name: "create", attributes: ["*"] }, + { name: ACTIONS.CREATE, attributes: ["*"] }, { - name: "update", + name: ACTIONS.UPDATE, attributes: ["status", "items", "delivery"], }, ], @@ -79,16 +79,16 @@ export const Roles: Role[] = [ { name: RESOURCES.PRODUCT, actions: [ - { name: "create", attributes: ["*", "active"] }, + { name: ACTIONS.CREATE, attributes: ["*", "active"] }, { - name: "read", + name: ACTIONS.READ, attributes: ["*", "!history"], scope: { valid: true, }, }, { - name: "update", + name: ACTIONS.UPDATE, attributes: ["*", "!history", "!active"], }, ], @@ -102,11 +102,11 @@ export const Roles: Role[] = [ name: RESOURCES.USER, actions: [ { - name: "update", + name: ACTIONS.UPDATE, attributes: ["*", "!password"], }, { - name: "update", + name: ACTIONS.UPDATE, attributes: ["firstName", "lastName"], }, ], @@ -114,10 +114,10 @@ export const Roles: Role[] = [ { name: RESOURCES.ORDER, actions: [ - { name: "read", attributes: ["*"] }, - { name: "update", attributes: ["status", "items"] }, + { name: ACTIONS.READ, attributes: ["*"] }, + { name: ACTIONS.UPDATE, attributes: ["status", "items"] }, { - name: "export", + name: ACTIONS.EXPORT, attributes: ["*"], }, ], @@ -126,7 +126,7 @@ export const Roles: Role[] = [ name: RESOURCES.PRODUCT, actions: [ { - name: "read", + name: ACTIONS.READ, attributes: ["*", "!isActive"], scope: { namespace: "*.merchant.products", From 34381c38007a51fe4b4c06a5d85601a9167883e7 Mon Sep 17 00:00:00 2001 From: Haithm EL-Watany Date: Wed, 14 Feb 2024 23:34:21 +0200 Subject: [PATCH 3/3] fix(simple-access): added missing type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🐛 `simpleAccess.ts`: Added missing param type in `getPermission` method --- src/simpleAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simpleAccess.ts b/src/simpleAccess.ts index 174f13e..6d7076d 100644 --- a/src/simpleAccess.ts +++ b/src/simpleAccess.ts @@ -236,7 +236,7 @@ export class SimpleAccess< * */ private getPermission( - role: Array | string, + role: Array | R[0], action: R[2], resource: R[1], roles: Array>