Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2826ffe

Browse files
committedNov 20, 2020
Allow number as valid role and grant type
In some cases it makes sense to have `number`s instead of `string`s for role types. For example, when using `enum`s in TypeScript
1 parent c91eb67 commit 2826ffe

31 files changed

+6371
-3988
lines changed
 

‎.travis.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
language: node_js
22
node_js:
3-
- '8'
4-
- '6'
3+
- '14'
4+
- '12'
5+
- '10'
56
before_script: cd $TRAVIS_BUILD_DIR
67
script:
78
- npm run cover

‎lib/AccessControl.d.ts

+19-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Access, IAccessInfo, Query, IQueryInfo, Permission } from './core';
2+
import type { ValidRoleOrArray, ValidRole } from '.';
23
/**
34
* @classdesc
45
* AccessControl class that implements RBAC (Role-Based Access Control) basics
@@ -118,7 +119,7 @@ declare class AccessControl {
118119
* @name AccessControl#isLocked
119120
* @type {Boolean}
120121
*/
121-
readonly isLocked: boolean;
122+
get isLocked(): boolean;
122123
/**
123124
* Gets the internal grants object that stores all current grants.
124125
*
@@ -234,7 +235,7 @@ declare class AccessControl {
234235
* @throws {AccessControlError} - If a role is extended by itself or a
235236
* non-existent role. Or if called after `.lock()` is called.
236237
*/
237-
extendRole(roles: string | string[], extenderRoles: string | string[]): AccessControl;
238+
extendRole(roles: ValidRoleOrArray, extenderRoles: ValidRoleOrArray): AccessControl;
238239
/**
239240
* Removes all the given role(s) and their granted permissions, at once.
240241
* @chainable
@@ -246,7 +247,7 @@ declare class AccessControl {
246247
*
247248
* @throws {AccessControlError} - If called after `.lock()` is called.
248249
*/
249-
removeRoles(roles: string | string[]): AccessControl;
250+
removeRoles(roles: ValidRoleOrArray): AccessControl;
250251
/**
251252
* Removes all the given resources for all roles, at once.
252253
* Pass the `roles` argument to remove access to resources for those
@@ -263,7 +264,7 @@ declare class AccessControl {
263264
*
264265
* @throws {AccessControlError} - If called after `.lock()` is called.
265266
*/
266-
removeResources(resources: string | string[], roles?: string | string[]): AccessControl;
267+
removeResources(resources: ValidRoleOrArray, roles?: ValidRoleOrArray): AccessControl;
267268
/**
268269
* Gets all the unique roles that have at least one access information.
269270
*
@@ -284,12 +285,12 @@ declare class AccessControl {
284285
*
285286
* @returns {Array<String>}
286287
*/
287-
getInheritedRolesOf(role: string): string[];
288+
getInheritedRolesOf(role: ValidRole): ValidRole[];
288289
/**
289290
* Alias of `getInheritedRolesOf`
290291
* @private
291292
*/
292-
getExtendedRolesOf(role: string): string[];
293+
getExtendedRolesOf(role: ValidRole): ValidRole[];
293294
/**
294295
* Gets all the unique resources that are granted access for at
295296
* least one role.
@@ -305,7 +306,7 @@ declare class AccessControl {
305306
*
306307
* @returns {Boolean}
307308
*/
308-
hasRole(role: string | string[]): boolean;
309+
hasRole(role: ValidRoleOrArray): boolean;
309310
/**
310311
* Checks whether grants include the given resource or resources.
311312
*
@@ -314,7 +315,7 @@ declare class AccessControl {
314315
*
315316
* @returns {Boolean}
316317
*/
317-
hasResource(resource: string | string[]): boolean;
318+
hasResource(resource: ValidRoleOrArray): boolean;
318319
/**
319320
* Gets an instance of `Query` object. This is used to check whether the
320321
* defined access is allowed for the given role(s) and resource. This
@@ -347,12 +348,12 @@ declare class AccessControl {
347348
* ac.can(['admin', 'user']).createOwn('profile');
348349
* // Note: when multiple roles checked, acquired attributes are unioned (merged).
349350
*/
350-
can(role: string | string[] | IQueryInfo): Query;
351+
can(role: ValidRoleOrArray | IQueryInfo): Query;
351352
/**
352353
* Alias of `can()`.
353354
* @private
354355
*/
355-
query(role: string | string[] | IQueryInfo): Query;
356+
query(role: ValidRoleOrArray | IQueryInfo): Query;
356357
/**
357358
* Gets an instance of `Permission` object that checks and defines the
358359
* granted access permissions for the target resource and role. Normally
@@ -437,12 +438,12 @@ declare class AccessControl {
437438
* // Note: when attributes is omitted, it will default to `['*']`
438439
* // which means all attributes (of the resource) are allowed.
439440
*/
440-
grant(role?: string | string[] | IAccessInfo): Access;
441+
grant(role?: ValidRoleOrArray | IAccessInfo): Access;
441442
/**
442443
* Alias of `grant()`.
443444
* @private
444445
*/
445-
allow(role?: string | string[] | IAccessInfo): Access;
446+
allow(role?: ValidRoleOrArray | IAccessInfo): Access;
446447
/**
447448
* Gets an instance of `Access` object. This is used to deny access to
448449
* specified resource(s) for the given role(s). Denying will only remove a
@@ -495,31 +496,31 @@ declare class AccessControl {
495496
* // To deny same resource for multiple roles:
496497
* ac.deny(['admin', 'user']).createOwn('profile');
497498
*/
498-
deny(role?: string | string[] | IAccessInfo): Access;
499+
deny(role?: ValidRoleOrArray | IAccessInfo): Access;
499500
/**
500501
* Alias of `deny()`.
501502
* @private
502503
*/
503-
reject(role?: string | string[] | IAccessInfo): Access;
504+
reject(role?: ValidRoleOrArray | IAccessInfo): Access;
504505
/**
505506
* @private
506507
*/
507-
_removePermission(resources: string | string[], roles?: string | string[], actionPossession?: string): void;
508+
_removePermission(resources: ValidRoleOrArray, roles?: ValidRoleOrArray, actionPossession?: string): void;
508509
/**
509510
* Documented separately in enums/Action
510511
* @private
511512
*/
512-
static readonly Action: any;
513+
static get Action(): any;
513514
/**
514515
* Documented separately in enums/Possession
515516
* @private
516517
*/
517-
static readonly Possession: any;
518+
static get Possession(): any;
518519
/**
519520
* Documented separately in AccessControlError
520521
* @private
521522
*/
522-
static readonly Error: any;
523+
static get Error(): any;
523524
/**
524525
* A utility method for deep cloning the given data object(s) while
525526
* filtering its properties by the given attribute (glob) notations.

‎lib/AccessControl.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.AccessControl = void 0;
34
var core_1 = require("./core");
45
var enums_1 = require("./enums");
56
var utils_1 = require("./utils");
@@ -130,7 +131,7 @@ var AccessControl = /** @class */ (function () {
130131
get: function () {
131132
return this._isLocked && Object.isFrozen(this._grants);
132133
},
133-
enumerable: true,
134+
enumerable: false,
134135
configurable: true
135136
});
136137
// -------------------------------
@@ -287,8 +288,8 @@ var AccessControl = /** @class */ (function () {
287288
var _this = this;
288289
if (this.isLocked)
289290
throw new core_1.AccessControlError(utils_1.ERR_LOCK);
290-
var rolesToRemove = utils_1.utils.toStringArray(roles);
291-
if (rolesToRemove.length === 0 || !utils_1.utils.isFilledStringArray(rolesToRemove)) {
291+
var rolesToRemove = utils_1.utils.toValidRoleArray(roles);
292+
if (rolesToRemove.length === 0 || !utils_1.utils.isFilledValidRoleArray(rolesToRemove)) {
292293
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles));
293294
}
294295
rolesToRemove.forEach(function (roleName) {
@@ -631,15 +632,15 @@ var AccessControl = /** @class */ (function () {
631632
*/
632633
AccessControl.prototype._removePermission = function (resources, roles, actionPossession) {
633634
var _this = this;
634-
resources = utils_1.utils.toStringArray(resources);
635+
resources = utils_1.utils.toValidRoleArray(resources);
635636
// resources is set but returns empty array.
636-
if (resources.length === 0 || !utils_1.utils.isFilledStringArray(resources)) {
637+
if (resources.length === 0 || !utils_1.utils.isFilledValidRoleArray(resources)) {
637638
throw new core_1.AccessControlError("Invalid resource(s): " + JSON.stringify(resources));
638639
}
639640
if (roles !== undefined) {
640-
roles = utils_1.utils.toStringArray(roles);
641+
roles = utils_1.utils.toValidRoleArray(roles);
641642
// roles is set but returns empty array.
642-
if (roles.length === 0 || !utils_1.utils.isFilledStringArray(roles)) {
643+
if (roles.length === 0 || !utils_1.utils.isFilledValidRoleArray(roles)) {
643644
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles));
644645
}
645646
}
@@ -673,7 +674,7 @@ var AccessControl = /** @class */ (function () {
673674
get: function () {
674675
return enums_1.Action;
675676
},
676-
enumerable: true,
677+
enumerable: false,
677678
configurable: true
678679
});
679680
Object.defineProperty(AccessControl, "Possession", {
@@ -684,7 +685,7 @@ var AccessControl = /** @class */ (function () {
684685
get: function () {
685686
return enums_1.Possession;
686687
},
687-
enumerable: true,
688+
enumerable: false,
688689
configurable: true
689690
});
690691
Object.defineProperty(AccessControl, "Error", {
@@ -695,7 +696,7 @@ var AccessControl = /** @class */ (function () {
695696
get: function () {
696697
return core_1.AccessControlError;
697698
},
698-
enumerable: true,
699+
enumerable: false,
699700
configurable: true
700701
});
701702
// -------------------------------

‎lib/core/Access.d.ts

+24-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { AccessControl } from '../';
22
import { IAccessInfo } from '../core';
3+
export declare type ValidRole = string | number;
4+
export declare type ValidRoleOrArray = ValidRole | ValidRole[];
35
/**
46
* Represents the inner `Access` class that helps build an access information
57
* to be granted or denied; and finally commits it to the underlying grants
@@ -44,38 +46,38 @@ declare class Access {
4446
* @param {Boolean} denied
4547
* Specifies whether this `Access` is denied.
4648
*/
47-
constructor(ac: AccessControl, roleOrInfo?: string | string[] | IAccessInfo, denied?: boolean);
49+
constructor(ac: AccessControl, roleOrInfo?: ValidRoleOrArray | IAccessInfo, denied?: boolean);
4850
/**
4951
* Specifies whether this access is initally denied.
5052
* @name AccessControl~Access#denied
5153
* @type {Boolean}
5254
* @readonly
5355
*/
54-
readonly denied: boolean;
56+
get denied(): boolean;
5557
/**
5658
* A chainer method that sets the role(s) for this `Access` instance.
5759
* @param {String|Array<String>} value
5860
* A single or array of roles.
5961
* @returns {Access}
6062
* Self instance of `Access`.
6163
*/
62-
role(value: string | string[]): Access;
64+
role(value: ValidRoleOrArray): Access;
6365
/**
6466
* A chainer method that sets the resource for this `Access` instance.
6567
* @param {String|Array<String>} value
6668
* Target resource for this `Access` instance.
6769
* @returns {Access}
6870
* Self instance of `Access`.
6971
*/
70-
resource(value: string | string[]): Access;
72+
resource(value: ValidRoleOrArray): Access;
7173
/**
7274
* Sets the array of allowed attributes for this `Access` instance.
7375
* @param {String|Array<String>} value
7476
* Attributes to be set.
7577
* @returns {Access}
7678
* Self instance of `Access`.
7779
*/
78-
attributes(value: string | string[]): Access;
80+
attributes(value: ValidRoleOrArray): Access;
7981
/**
8082
* Sets the roles to be extended for this `Access` instance.
8183
* @alias Access#inherit
@@ -93,12 +95,12 @@ declare class Access {
9395
* const permission = ac.can('admin').createAny('video');
9496
* console.log(permission.granted); // true
9597
*/
96-
extend(roles: string | string[]): Access;
98+
extend(roles: ValidRoleOrArray): Access;
9799
/**
98100
* Alias of `extend`.
99101
* @private
100102
*/
101-
inherit(roles: string | string[]): Access;
103+
inherit(roles: ValidRoleOrArray): Access;
102104
/**
103105
* Shorthand to switch to a new `Access` instance with a different role
104106
* within the method chain.
@@ -114,7 +116,7 @@ declare class Access {
114116
* ac.grant('user').createOwn('video')
115117
* .grant('admin').updateAny('video');
116118
*/
117-
grant(roleOrInfo?: string | string[] | IAccessInfo): Access;
119+
grant(roleOrInfo?: ValidRoleOrArray | IAccessInfo): Access;
118120
/**
119121
* Shorthand to switch to a new `Access` instance with a different
120122
* (or same) role within the method chain.
@@ -130,7 +132,7 @@ declare class Access {
130132
* ac.grant('admin').createAny('video')
131133
* .deny('user').deleteAny('video');
132134
*/
133-
deny(roleOrInfo?: string | string[] | IAccessInfo): Access;
135+
deny(roleOrInfo?: ValidRoleOrArray | IAccessInfo): Access;
134136
/**
135137
* Chainable, convenience shortcut for {@link ?api=ac#AccessControl#lock|`AccessControl#lock()`}.
136138
* @returns {Access}
@@ -159,7 +161,7 @@ declare class Access {
159161
* Self instance of `Access` so that you can chain and define
160162
* another access instance to be committed.
161163
*/
162-
createOwn(resource?: string | string[], attributes?: string | string[]): Access;
164+
createOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
163165
/**
164166
* Sets the action to `"create"` and possession to `"any"` and commits the
165167
* current access instance to the underlying grant model.
@@ -185,12 +187,12 @@ declare class Access {
185187
* Self instance of `Access` so that you can chain and define
186188
* another access instance to be committed.
187189
*/
188-
createAny(resource?: string | string[], attributes?: string | string[]): Access;
190+
createAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
189191
/**
190192
* Alias of `createAny`
191193
* @private
192194
*/
193-
create(resource?: string | string[], attributes?: string | string[]): Access;
195+
create(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
194196
/**
195197
* Sets the action to `"read"` and possession to `"own"` and commits the
196198
* current access instance to the underlying grant model.
@@ -213,7 +215,7 @@ declare class Access {
213215
* Self instance of `Access` so that you can chain and define
214216
* another access instance to be committed.
215217
*/
216-
readOwn(resource?: string | string[], attributes?: string | string[]): Access;
218+
readOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
217219
/**
218220
* Sets the action to `"read"` and possession to `"any"` and commits the
219221
* current access instance to the underlying grant model.
@@ -239,12 +241,12 @@ declare class Access {
239241
* Self instance of `Access` so that you can chain and define
240242
* another access instance to be committed.
241243
*/
242-
readAny(resource?: string | string[], attributes?: string | string[]): Access;
244+
readAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
243245
/**
244246
* Alias of `readAny`
245247
* @private
246248
*/
247-
read(resource?: string | string[], attributes?: string | string[]): Access;
249+
read(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
248250
/**
249251
* Sets the action to `"update"` and possession to `"own"` and commits the
250252
* current access instance to the underlying grant model.
@@ -267,7 +269,7 @@ declare class Access {
267269
* Self instance of `Access` so that you can chain and define
268270
* another access instance to be committed.
269271
*/
270-
updateOwn(resource?: string | string[], attributes?: string | string[]): Access;
272+
updateOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
271273
/**
272274
* Sets the action to `"update"` and possession to `"any"` and commits the
273275
* current access instance to the underlying grant model.
@@ -293,12 +295,12 @@ declare class Access {
293295
* Self instance of `Access` so that you can chain and define
294296
* another access instance to be committed.
295297
*/
296-
updateAny(resource?: string | string[], attributes?: string | string[]): Access;
298+
updateAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
297299
/**
298300
* Alias of `updateAny`
299301
* @private
300302
*/
301-
update(resource?: string | string[], attributes?: string | string[]): Access;
303+
update(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
302304
/**
303305
* Sets the action to `"delete"` and possession to `"own"` and commits the
304306
* current access instance to the underlying grant model.
@@ -321,7 +323,7 @@ declare class Access {
321323
* Self instance of `Access` so that you can chain and define
322324
* another access instance to be committed.
323325
*/
324-
deleteOwn(resource?: string | string[], attributes?: string | string[]): Access;
326+
deleteOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
325327
/**
326328
* Sets the action to `"delete"` and possession to `"any"` and commits the
327329
* current access instance to the underlying grant model.
@@ -347,12 +349,12 @@ declare class Access {
347349
* Self instance of `Access` so that you can chain and define
348350
* another access instance to be committed.
349351
*/
350-
deleteAny(resource?: string | string[], attributes?: string | string[]): Access;
352+
deleteAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
351353
/**
352354
* Alias of `deleteAny`
353355
* @private
354356
*/
355-
delete(resource?: string | string[], attributes?: string | string[]): Access;
357+
delete(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access;
356358
/**
357359
* @private
358360
* @param {String} action [description]
@@ -362,6 +364,6 @@ declare class Access {
362364
* @returns {Access}
363365
* Self instance of `Access`.
364366
*/
365-
private _prepareAndCommit(action, possession, resource?, attributes?);
367+
private _prepareAndCommit;
366368
}
367369
export { Access };

‎lib/core/Access.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.Access = void 0;
34
var core_1 = require("../core");
45
var enums_1 = require("../enums");
56
var utils_1 = require("../utils");
@@ -40,7 +41,7 @@ var Access = /** @class */ (function () {
4041
this._ac = ac;
4142
this._grants = ac._grants;
4243
this._.denied = denied;
43-
if (typeof roleOrInfo === 'string' || Array.isArray(roleOrInfo)) {
44+
if (typeof roleOrInfo === 'string' || typeof roleOrInfo === 'number' || Array.isArray(roleOrInfo)) {
4445
this.role(roleOrInfo);
4546
}
4647
else if (utils_1.utils.type(roleOrInfo) === 'object') {
@@ -73,7 +74,7 @@ var Access = /** @class */ (function () {
7374
get: function () {
7475
return this._.denied;
7576
},
76-
enumerable: true,
77+
enumerable: false,
7778
configurable: true
7879
});
7980
// -------------------------------
@@ -453,7 +454,7 @@ var Access = /** @class */ (function () {
453454
}
454455
else {
455456
// if omitted and not denied, all attributes are allowed
456-
this._.attributes = attributes ? utils_1.utils.toStringArray(attributes) : ['*'];
457+
this._.attributes = attributes ? utils_1.utils.toValidRoleArray(attributes) : ['*'];
457458
}
458459
utils_1.utils.commitToGrants(this._grants, this._, false);
459460
// important: reset attributes for chained methods

‎lib/core/AccessControlError.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
"use strict";
22
var __extends = (this && this.__extends) || (function () {
3-
var extendStatics = Object.setPrototypeOf ||
4-
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5-
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
3+
var extendStatics = function (d, b) {
4+
extendStatics = Object.setPrototypeOf ||
5+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7+
return extendStatics(d, b);
8+
};
69
return function (d, b) {
710
extendStatics(d, b);
811
function __() { this.constructor = d; }
912
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
1013
};
1114
})();
1215
Object.defineProperty(exports, "__esModule", { value: true });
16+
exports.AccessControlError = void 0;
1317
/**
1418
* Error class specific to `AccessControl`.
1519
* @readonly

‎lib/core/IAccessInfo.d.ts

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ValidRoleOrArray } from '.';
12
/**
23
* An interface that defines an access information to be granted or denied.
34
* When you start a method chain with `AccessControl#grant` or `AccessControl#deny`
@@ -10,19 +11,19 @@ interface IAccessInfo {
1011
* Indicates a single or multiple roles for this access information.
1112
* @type {String|Array<String>}
1213
*/
13-
role?: string | string[];
14+
role?: ValidRoleOrArray;
1415
/**
1516
* Indicates a single or multiple target resources for this access
1617
* information.
1718
* @type {String|Array<String>}
1819
*/
19-
resource?: string | string[];
20+
resource?: ValidRoleOrArray;
2021
/**
2122
* Defines the resource attributes which are granted. If denied, this will
2223
* default to an empty array.
2324
* @type {String|Array<String>}
2425
*/
25-
attributes?: string | string[];
26+
attributes?: ValidRoleOrArray;
2627
/**
2728
* Defines the type of the operation that is (or not) to be performed on
2829
* the resource(s) by the defined role(s).
@@ -39,10 +40,40 @@ interface IAccessInfo {
3940
*/
4041
possession?: string;
4142
/**
42-
* Single or multiple roles for this access information.
43+
* Flag for denied access.
4344
* @private
4445
* @type {String|Array<String>}
4546
*/
4647
denied?: boolean;
4748
}
4849
export { IAccessInfo };
50+
/**
51+
* An interface that defines an access information to be granted or denied.
52+
* When you start a method chain with `AccessControl#grant` or `AccessControl#deny`
53+
* methods, you're actually building this object which will eventually be
54+
* committed to the underlying grants model.
55+
* @name AccessControl~IAccessInfo
56+
* @type {Object}
57+
*
58+
* @property {String|Array<String>} role
59+
* Indicates a single or multiple roles for this access information.
60+
*
61+
* @property {String|Array<String>} resource
62+
* Indicates a single or multiple target resources for this access
63+
* information.
64+
*
65+
* @property {String|Array<String>} attributes
66+
* Defines the resource attributes which are granted. If denied, this will
67+
* default to an empty array.
68+
*
69+
* @property {String} action
70+
* Defines the type of the operation that is (or not) to be performed on
71+
* the resource(s) by the defined role(s).
72+
* See {@link ?api=ac#AccessControl.Action|`AccessControl.Action` enumeration}
73+
* for possible values.
74+
*
75+
* @property {String} possession
76+
* Defines the possession of the resource(s) for the specified action.
77+
* See {@link ?api=ac#AccessControl.Possession|`AccessControl.Possession` enumeration}
78+
* for possible values.
79+
*/

‎lib/core/IQueryInfo.d.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ValidRoleOrArray } from '.';
12
/**
23
* An interface that defines an access information to be queried.
34
* When you start a method chain with `AccessControl#can` method, you're
@@ -10,7 +11,7 @@ interface IQueryInfo {
1011
* Indicates a single or multiple roles to be queried.
1112
* @type {String|Array<String>}
1213
*/
13-
role?: string | string[];
14+
role?: ValidRoleOrArray;
1415
/**
1516
* Indicates the resource to be queried.
1617
* @type {String}
@@ -33,3 +34,28 @@ interface IQueryInfo {
3334
possession?: string;
3435
}
3536
export { IQueryInfo };
37+
/**
38+
* An interface that defines an access information to be queried.
39+
* When you start a method chain with `AccessControl#can` method, you're
40+
* actually building this query object which will be used to check the access
41+
* permissions.
42+
* @name AccessControl~IQueryInfo
43+
* @type {Object}
44+
*
45+
* @property {String|Array<String>} role
46+
* Indicates a single or multiple roles to be queried.
47+
*
48+
* @property {String} resource
49+
* Indicates the resource to be queried.
50+
*
51+
* @property {String} action
52+
* Defines the type of the operation that is (or not) to be performed on
53+
* the resource by the defined role(s).
54+
* See {@link ?api=ac#AccessControl.Action|`AccessControl.Action` enumeration}
55+
* for possible values.
56+
*
57+
* @property {String} possession
58+
* Defines the possession of the resource for the specified action.
59+
* See {@link ?api=ac#AccessControl.Possession|`AccessControl.Possession` enumeration}
60+
* for possible values.
61+
*/

‎lib/core/Permission.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ declare class Permission {
6060
* @type {Array<String>}
6161
* @readonly
6262
*/
63-
readonly roles: string[];
63+
get roles(): string[];
6464
/**
6565
* Specifies the target resource for which the permission is queried for.
6666
*
6767
* @name AccessControl~Permission#resource
6868
* @type {String}
6969
* @readonly
7070
*/
71-
readonly resource: string;
71+
get resource(): string;
7272
/**
7373
* Gets an array of allowed attributes which are defined via
7474
* Glob notation. If access is not granted, this will be an empty array.
@@ -81,7 +81,7 @@ declare class Permission {
8181
* @type {Array<String>}
8282
* @readonly
8383
*/
84-
readonly attributes: string[];
84+
get attributes(): string[];
8585
/**
8686
* Specifies whether the permission is granted. If `true`, this means at
8787
* least one attribute of the target resource is allowed.
@@ -90,7 +90,7 @@ declare class Permission {
9090
* @type {Boolean}
9191
* @readonly
9292
*/
93-
readonly granted: boolean;
93+
get granted(): boolean;
9494
/**
9595
* Filters the given data object (or array of objects) by the permission
9696
* attributes and returns this data with allowed attributes.

‎lib/core/Permission.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.Permission = void 0;
34
var utils_1 = require("../utils");
45
/**
56
* Represents the inner `Permission` class that defines the granted or denied
@@ -71,7 +72,7 @@ var Permission = /** @class */ (function () {
7172
get: function () {
7273
return this._.role;
7374
},
74-
enumerable: true,
75+
enumerable: false,
7576
configurable: true
7677
});
7778
Object.defineProperty(Permission.prototype, "resource", {
@@ -85,7 +86,7 @@ var Permission = /** @class */ (function () {
8586
get: function () {
8687
return this._.resource;
8788
},
88-
enumerable: true,
89+
enumerable: false,
8990
configurable: true
9091
});
9192
Object.defineProperty(Permission.prototype, "attributes", {
@@ -104,7 +105,7 @@ var Permission = /** @class */ (function () {
104105
get: function () {
105106
return this._.attributes;
106107
},
107-
enumerable: true,
108+
enumerable: false,
108109
configurable: true
109110
});
110111
Object.defineProperty(Permission.prototype, "granted", {
@@ -124,7 +125,7 @@ var Permission = /** @class */ (function () {
124125
return attr.trim().slice(0, 1) !== '!';
125126
});
126127
},
127-
enumerable: true,
128+
enumerable: false,
128129
configurable: true
129130
});
130131
/**

‎lib/core/Query.d.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IQueryInfo, Permission } from '../core';
2+
import type { ValidRoleOrArray } from '.';
23
/**
34
* Represents the inner `Query` class that helps build an access information
45
* for querying and checking permissions, from the underlying grants model.
@@ -32,15 +33,15 @@ declare class Query {
3233
* Either a single or array of roles or an
3334
* {@link ?api=ac#AccessControl~IQueryInfo|`IQueryInfo` arbitrary object}.
3435
*/
35-
constructor(grants: any, roleOrInfo?: string | string[] | IQueryInfo);
36+
constructor(grants: any, roleOrInfo?: ValidRoleOrArray | IQueryInfo);
3637
/**
3738
* A chainer method that sets the role(s) for this `Query` instance.
3839
* @param {String|Array<String>} roles
3940
* A single or array of roles.
4041
* @returns {Query}
4142
* Self instance of `Query`.
4243
*/
43-
role(role: string | string[]): Query;
44+
role(role: ValidRoleOrArray): Query;
4445
/**
4546
* A chainer method that sets the resource for this `Query` instance.
4647
* @param {String} resource
@@ -212,6 +213,6 @@ declare class Query {
212213
* @param {String} [resource]
213214
* @returns {Permission}
214215
*/
215-
private _getPermission(action, possession, resource?);
216+
private _getPermission;
216217
}
217218
export { Query };

‎lib/core/Query.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.Query = void 0;
34
var core_1 = require("../core");
45
var enums_1 = require("../enums");
56
var utils_1 = require("../utils");

‎lib/core/index.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
"use strict";
2-
function __export(m) {
3-
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4-
}
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5+
}) : (function(o, m, k, k2) {
6+
if (k2 === undefined) k2 = k;
7+
o[k2] = m[k];
8+
}));
9+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
10+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11+
};
512
Object.defineProperty(exports, "__esModule", { value: true });
6-
__export(require("./AccessControlError"));
7-
__export(require("./Access"));
8-
__export(require("./Query"));
9-
__export(require("./Permission"));
13+
__exportStar(require("./AccessControlError"), exports);
14+
__exportStar(require("./IAccessInfo"), exports);
15+
__exportStar(require("./Access"), exports);
16+
__exportStar(require("./IQueryInfo"), exports);
17+
__exportStar(require("./Query"), exports);
18+
__exportStar(require("./Permission"), exports);

‎lib/enums/Action.d.ts

+20
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,29 @@
88
* @memberof! AccessControl
99
*/
1010
declare const Action: {
11+
/**
12+
* Specifies a CREATE action to be performed on a resource.
13+
* For example, an HTTP POST request or an INSERT database operation.
14+
* @type {String}
15+
*/
1116
CREATE: string;
17+
/**
18+
* Specifies a READ action to be performed on a resource.
19+
* For example, an HTTP GET request or an database SELECT operation.
20+
* @type {String}
21+
*/
1222
READ: string;
23+
/**
24+
* Specifies an UPDATE action to be performed on a resource.
25+
* For example, an HTTP PUT or POST request or an database UPDATE operation.
26+
* @type {String}
27+
*/
1328
UPDATE: string;
29+
/**
30+
* Specifies a DELETE action to be performed on a resource.
31+
* For example, an HTTP DELETE request or a database DELETE operation.
32+
* @type {String}
33+
*/
1434
DELETE: string;
1535
};
1636
export { Action };

‎lib/enums/Action.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.Action = void 0;
34
/**
45
* Enumerates the possible actions of a role.
56
* An action defines the type of an operation that will be executed on a

‎lib/enums/Possession.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@
77
* @memberof! AccessControl
88
*/
99
declare const Possession: {
10+
/**
11+
* Indicates that the action is (or not) to be performed on <b>own</b>
12+
* resource(s) of the current subject.
13+
* @type {String}
14+
*/
1015
OWN: string;
16+
/**
17+
* Indicates that the action is (or not) to be performed on <b>any</b>
18+
* resource(s); including <i>own</i> resource(s) of the current subject.
19+
* @type {String}
20+
*/
1121
ANY: string;
1222
};
1323
export { Possession };

‎lib/enums/Possession.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.Possession = void 0;
34
/**
45
* Enumerates the possible possessions of a resource, for an action.
56
* A possession defines whether the action is (or not) to be performed on "own"

‎lib/enums/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.possessions = exports.Possession = exports.actions = exports.Action = void 0;
34
var Action_1 = require("./Action");
4-
exports.Action = Action_1.Action;
5+
Object.defineProperty(exports, "Action", { enumerable: true, get: function () { return Action_1.Action; } });
56
var Possession_1 = require("./Possession");
6-
exports.Possession = Possession_1.Possession;
7+
Object.defineProperty(exports, "Possession", { enumerable: true, get: function () { return Possession_1.Possession; } });
78
var actions = Object.keys(Action_1.Action).map(function (k) { return Action_1.Action[k]; });
89
exports.actions = actions;
910
var possessions = Object.keys(Possession_1.Possession).map(function (k) { return Possession_1.Possession[k]; });

‎lib/index.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"use strict";
2-
function __export(m) {
3-
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4-
}
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5+
}) : (function(o, m, k, k2) {
6+
if (k2 === undefined) k2 = k;
7+
o[k2] = m[k];
8+
}));
9+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
10+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11+
};
512
Object.defineProperty(exports, "__esModule", { value: true });
6-
__export(require("./AccessControl"));
7-
__export(require("./core"));
13+
__exportStar(require("./AccessControl"), exports);
14+
__exportStar(require("./core"), exports);

‎lib/utils.d.ts

+300-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AccessControl } from './';
22
import { IAccessInfo, IQueryInfo } from './core';
3+
import type { ValidRole, ValidRoleOrArray } from '.';
34
/**
45
* List of reserved keywords.
56
* i.e. Roles, resources with these names are not allowed.
@@ -10,40 +11,326 @@ declare const RESERVED_KEYWORDS: string[];
1011
*/
1112
declare const ERR_LOCK = "Cannot alter the underlying grants model. AccessControl instance is locked.";
1213
declare const utils: {
14+
/**
15+
* Gets the type of the given object.
16+
* @param {Any} o
17+
* @returns {String}
18+
*/
1319
type(o: any): string;
20+
/**
21+
* Specifies whether the given value is set (other that `null` or
22+
* `undefined`).
23+
* @param {Any} o - Value to be checked.
24+
* @returns {Boolean}
25+
*/
26+
/**
27+
* Specifies whether the property/key is defined on the given object.
28+
* @param {Object} o
29+
* @param {string} propName
30+
* @returns {Boolean}
31+
*/
1432
hasDefined(o: any, propName: string): boolean;
15-
toStringArray(value: any): string[];
16-
isFilledStringArray(arr: any[]): boolean;
33+
/**
34+
* Converts the given (string) value into an array of string. Note that
35+
* this does not throw if the value is not a string or array. It will
36+
* silently return `[]` (empty array). So where ever it's used, the host
37+
* function should consider throwing.
38+
* @param {Any} value
39+
* @returns {string[]}
40+
*/
41+
toValidRoleArray(value: unknown): ValidRole[];
42+
/**
43+
* Checks whether the given array consists of non-empty string items.
44+
* (Array can be empty but no item should be an empty string.)
45+
* @param {Array} arr - Array to be checked.
46+
* @returns {Boolean}
47+
*/
48+
isFilledValidRoleArray(arr: unknown[]): arr is (string | number)[];
49+
isValidRole(e: unknown): e is string | number;
50+
/**
51+
* Checks whether the given value is an empty array.
52+
* @param {Any} value - Value to be checked.
53+
* @returns {Boolean}
54+
*/
1755
isEmptyArray(value: any): boolean;
18-
pushUniq(arr: string[], item: string): string[];
19-
uniqConcat(arrA: string[], arrB: string[]): string[];
20-
subtractArray(arrA: string[], arrB: string[]): string[];
56+
/**
57+
* Ensures that the pushed item is unique in the target array.
58+
* @param {Array} arr - Target array.
59+
* @param {Any} item - Item to be pushed to array.
60+
* @returns {Array}
61+
*/
62+
pushUniq(arr: ValidRole[], item: ValidRole): ValidRole[];
63+
/**
64+
* Concats the given two arrays and ensures all items are unique.
65+
* @param {Array} arrA
66+
* @param {Array} arrB
67+
* @returns {Array} - Concat'ed array.
68+
*/
69+
uniqConcat(arrA: ValidRole[], arrB: ValidRole[]): ValidRole[];
70+
/**
71+
* Subtracts the second array from the first.
72+
* @param {Array} arrA
73+
* @param {Array} arrB
74+
* @return {Array} - Resulting array.
75+
*/
76+
subtractArray(arrA: ValidRole[], arrB: ValidRole[]): ValidRole[];
77+
/**
78+
* Deep freezes the given object.
79+
* @param {Object} o - Object to be frozen.
80+
* @returns {Object} - Frozen object.
81+
*/
2182
deepFreeze(o: any): any;
83+
/**
84+
* Similar to JS .forEach, except this allows for breaking out early,
85+
* (before all iterations are executed) by returning `false`.
86+
* @param array
87+
* @param callback
88+
* @param thisArg
89+
*/
2290
each(array: any, callback: any, thisArg?: any): void;
91+
/**
92+
* Iterates through the keys of the given object. Breaking out early is
93+
* possible by returning `false`.
94+
* @param object
95+
* @param callback
96+
* @param thisArg
97+
*/
2398
eachKey(object: any, callback: any, thisArg?: any): void;
2499
eachRole(grants: any, callback: (role: any, roleName: string) => void): void;
100+
/**
101+
*
102+
*/
25103
eachRoleResource(grants: any, callback: (role: string, resource: string, resourceDefinition: any) => void): void;
104+
/**
105+
* Checks whether the given access info can be commited to grants model.
106+
* @param {IAccessInfo|IQueryInfo} info
107+
* @returns {Boolean}
108+
*/
26109
isInfoFulfilled(info: IAccessInfo | IQueryInfo): boolean;
27-
validName(name: string, throwOnInvalid?: boolean): boolean;
110+
/**
111+
* Checks whether the given name can be used and is not a reserved keyword.
112+
*
113+
* @param {string} name - Name to be checked.
114+
* @param {boolean} [throwOnInvalid=true] - Specifies whether to throw if
115+
* name is not valid.
116+
*
117+
* @returns {Boolean}
118+
*
119+
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and name
120+
* is invalid.
121+
*/
122+
validName(name: ValidRole, throwOnInvalid?: boolean): boolean;
123+
/**
124+
* Checks whether the given array does not contain a reserved keyword.
125+
*
126+
* @param {string|string[]} list - Name(s) to be checked.
127+
* @param {boolean} [throwOnInvalid=true] - Specifies whether to throw if
128+
* name is not valid.
129+
*
130+
* @returns {Boolean}
131+
*
132+
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and name
133+
* is invalid.
134+
*/
28135
hasValidNames(list: any, throwOnInvalid?: boolean): boolean;
136+
/**
137+
* Checks whether the given object is a valid resource definition object.
138+
*
139+
* @param {Object} o - Resource definition to be checked.
140+
*
141+
* @returns {Boolean}
142+
*
143+
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and object
144+
* is invalid.
145+
*/
29146
validResourceObject(o: any): boolean;
147+
/**
148+
* Checks whether the given object is a valid role definition object.
149+
*
150+
* @param {Object} grants - Original grants object being inspected.
151+
* @param {string} roleName - Name of the role.
152+
*
153+
* @returns {Boolean}
154+
*
155+
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and object
156+
* is invalid.
157+
*/
30158
validRoleObject(grants: any, roleName: string): boolean;
159+
/**
160+
* Inspects whether the given grants object has a valid structure and
161+
* configuration; and returns a restructured grants object that can be used
162+
* internally by AccessControl.
163+
*
164+
* @param {Object|Array} o - Original grants object to be inspected.
165+
*
166+
* @returns {Object} - Inspected, restructured grants object.
167+
*
168+
* @throws {AccessControlError} - If given grants object has an invalid
169+
* structure or configuration.
170+
*/
31171
getInspectedGrants(o: any): any;
172+
/**
173+
* Gets all the unique resources that are granted access for at
174+
* least one role.
175+
*
176+
* @returns {string[]}
177+
*/
32178
getResources(grants: any): string[];
33-
normalizeActionPossession(info: IAccessInfo | IQueryInfo, asString?: boolean): string | IAccessInfo | IQueryInfo;
179+
/**
180+
* Normalizes the actions and possessions in the given `IQueryInfo` or
181+
* `IAccessInfo`.
182+
*
183+
* @param {IQueryInfo|IAccessInfo} info
184+
* @param {boolean} [asString=false]
185+
*
186+
* @return {IQueryInfo|IAccessInfo|string}
187+
*
188+
* @throws {AccessControlError} - If invalid action/possession found.
189+
*/
190+
normalizeActionPossession(info: IQueryInfo | IAccessInfo, asString?: boolean): IQueryInfo | IAccessInfo | string;
191+
/**
192+
* Normalizes the roles and resources in the given `IQueryInfo`.
193+
*
194+
* @param {IQueryInfo} info
195+
*
196+
* @return {IQueryInfo}
197+
*
198+
* @throws {AccessControlError} - If invalid role/resource found.
199+
*/
34200
normalizeQueryInfo(query: IQueryInfo): IQueryInfo;
201+
/**
202+
* Normalizes the roles and resources in the given `IAccessInfo`.
203+
*
204+
* @param {IAccessInfo} info
205+
* @param {boolean} [all=false] - Whether to validate all properties such
206+
* as `action` and `possession`.
207+
*
208+
* @return {IQueryInfo}
209+
*
210+
* @throws {AccessControlError} - If invalid role/resource found.
211+
*/
35212
normalizeAccessInfo(access: IAccessInfo, all?: boolean): IAccessInfo;
213+
/**
214+
* Used to re-set (prepare) the `attributes` of an `IAccessInfo` object
215+
* when it's first initialized with e.g. `.grant()` or `.deny()` chain
216+
* methods.
217+
* @param {IAccessInfo} access
218+
* @returns {IAccessInfo}
219+
*/
36220
resetAttributes(access: IAccessInfo): IAccessInfo;
37-
getRoleHierarchyOf(grants: any, roleName: string, rootRole?: string): string[];
38-
getFlatRoles(grants: any, roles: string | string[]): string[];
39-
getNonExistentRoles(grants: any, roles: string[]): string[];
40-
getCrossExtendingRole(grants: any, roleName: string, extenderRoles: string | string[]): string;
41-
extendRole(grants: any, roles: string | string[], extenderRoles: string | string[]): void;
42-
preCreateRoles(grants: any, roles: string | string[]): void;
221+
/**
222+
* Gets a flat, ordered list of inherited roles for the given role.
223+
* @param {Object} grants - Main grants object to be processed.
224+
* @param {string} roleName - Role name to be inspected.
225+
* @returns {string[]}
226+
*/
227+
getRoleHierarchyOf(grants: any, roleName: ValidRole, rootRole?: ValidRole): ValidRole[];
228+
/**
229+
* Gets roles and extended roles in a flat array.
230+
*/
231+
getFlatRoles(grants: any, roles: ValidRoleOrArray): ValidRole[];
232+
/**
233+
* Checks the given grants model and gets an array of non-existent roles
234+
* from the given roles.
235+
* @param {Any} grants - Grants model to be checked.
236+
* @param {string[]} roles - Roles to be checked.
237+
* @returns {ValidRole[]} - Array of non-existent roles. Empty array if
238+
* all exist.
239+
*/
240+
getNonExistentRoles(grants: any, roles: ValidRole[]): (string | number)[];
241+
/**
242+
* Checks whether the given extender role(s) is already (cross) inherited
243+
* by the given role and returns the first cross-inherited role. Otherwise,
244+
* returns `false`.
245+
*
246+
* Note that cross-inheritance is not allowed.
247+
*
248+
* @param {Any} grants - Grants model to be checked.
249+
* @param {string} roles - Target role to be checked.
250+
* @param {string|string[]} extenderRoles - Extender role(s) to be checked.
251+
*
252+
* @returns {string|null} - Returns the first cross extending role. `null`
253+
* if none.
254+
*/
255+
getCrossExtendingRole(grants: any, roleName: string, extenderRoles: ValidRoleOrArray): string;
256+
/**
257+
* Extends the given role(s) with privileges of one or more other roles.
258+
*
259+
* @param {Any} grants
260+
* @param {string|string[]} roles Role(s) to be extended. Single role
261+
* as a `String` or multiple roles as an `Array`. Note that if a
262+
* role does not exist, it will be automatically created.
263+
*
264+
* @param {string|string[]} extenderRoles Role(s) to inherit from.
265+
* Single role as a `String` or multiple roles as an `Array`. Note
266+
* that if a extender role does not exist, it will throw.
267+
*
268+
* @throws {Error} If a role is extended by itself, a non-existent role or
269+
* a cross-inherited role.
270+
*/
271+
extendRole(grants: any, roles: ValidRoleOrArray, extenderRoles: ValidRoleOrArray): void;
272+
/**
273+
* `utils.commitToGrants()` method already creates the roles but it's
274+
* executed when the chain is terminated with either `.extend()` or an
275+
* action method (e.g. `.createOwn()`). In case the chain is not
276+
* terminated, we'll still (pre)create the role(s) with an empty object.
277+
* @param {Any} grants
278+
* @param {string|string[]} roles
279+
*/
280+
preCreateRoles(grants: any, roles: ValidRoleOrArray): void;
281+
/**
282+
* Commits the given `IAccessInfo` object to the grants model.
283+
* CAUTION: if attributes is omitted, it will default to `['*']` which
284+
* means "all attributes allowed".
285+
* @param {Any} grants
286+
* @param {IAccessInfo} access
287+
* @param {boolean} normalizeAll
288+
* Specifies whether to validate and normalize all properties of
289+
* the inner `IAccessInfo` object, including `action` and `possession`.
290+
* @throws {Error} If `IAccessInfo` object fails validation.
291+
*/
43292
commitToGrants(grants: any, access: IAccessInfo, normalizeAll?: boolean): void;
293+
/**
294+
* When more than one role is passed, we union the permitted attributes
295+
* for all given roles; so we can check whether "at least one of these
296+
* roles" have the permission to execute this action.
297+
* e.g. `can(['admin', 'user']).createAny('video')`
298+
*
299+
* @param {Any} grants
300+
* @param {IQueryInfo} query
301+
*
302+
* @returns {string[]} - Array of union'ed attributes.
303+
*/
44304
getUnionAttrsOfRoles(grants: any, query: IQueryInfo): string[];
305+
/**
306+
* Locks the given AccessControl instance by freezing underlying grants
307+
* model and disabling all functionality to modify it.
308+
* @param {AccessControl} ac
309+
*/
45310
lockAC(ac: AccessControl): void;
311+
/**
312+
* Deep clones the source object while filtering its properties by the
313+
* given attributes (glob notations). Includes all matched properties and
314+
* removes the rest.
315+
*
316+
* @param {Object} object - Object to be filtered.
317+
* @param {string[]} attributes - Array of glob notations.
318+
*
319+
* @returns {Object} - Filtered object.
320+
*/
46321
filter(object: any, attributes: string[]): any;
322+
/**
323+
* Deep clones the source array of objects or a single object while
324+
* filtering their properties by the given attributes (glob notations).
325+
* Includes all matched properties and removes the rest of each object in
326+
* the array.
327+
*
328+
* @param {Array|Object} arrOrObj - Array of objects or single object to be
329+
* filtered.
330+
* @param {string[]} attributes - Array of glob notations.
331+
*
332+
* @returns {Array|Object}
333+
*/
47334
filterAll(arrOrObj: any, attributes: string[]): any;
48335
};
49336
export { utils, RESERVED_KEYWORDS, ERR_LOCK };

‎lib/utils.js

+31-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.ERR_LOCK = exports.RESERVED_KEYWORDS = exports.utils = void 0;
34
// dep modules
45
var Notation = require("notation");
56
var enums_1 = require("./enums");
@@ -57,11 +58,13 @@ var utils = {
5758
* @param {Any} value
5859
* @returns {string[]}
5960
*/
60-
toStringArray: function (value) {
61+
toValidRoleArray: function (value) {
6162
if (Array.isArray(value))
6263
return value;
6364
if (typeof value === 'string')
6465
return value.trim().split(/\s*[;,]\s*/);
66+
if (typeof value === 'number')
67+
return [value];
6568
// throw new Error('Expected a string or array of strings, got ' + utils.type(value));
6669
return [];
6770
},
@@ -71,14 +74,19 @@ var utils = {
7174
* @param {Array} arr - Array to be checked.
7275
* @returns {Boolean}
7376
*/
74-
isFilledStringArray: function (arr) {
77+
isFilledValidRoleArray: function (arr) {
7578
if (!arr || !Array.isArray(arr))
7679
return false;
7780
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
78-
var s = arr_1[_i];
79-
if (typeof s !== 'string' || s.trim() === '')
81+
var e = arr_1[_i];
82+
if (typeof e !== 'number' && (typeof e !== 'string' || e.trim() === ''))
8083
return false;
8184
}
85+
return arr.every(this.isValidRole);
86+
},
87+
isValidRole: function (e) {
88+
if (typeof e !== 'number' && (typeof e !== 'string' || e.trim() === ''))
89+
return false;
8290
return true;
8391
},
8492
/**
@@ -221,7 +229,7 @@ var utils = {
221229
*/
222230
validName: function (name, throwOnInvalid) {
223231
if (throwOnInvalid === void 0) { throwOnInvalid = true; }
224-
if (typeof name !== 'string' || name.trim() === '') {
232+
if (!this.isValidRole(name)) {
225233
if (!throwOnInvalid)
226234
return false;
227235
throw new core_1.AccessControlError('Invalid name, expected a valid string.');
@@ -248,7 +256,7 @@ var utils = {
248256
hasValidNames: function (list, throwOnInvalid) {
249257
if (throwOnInvalid === void 0) { throwOnInvalid = true; }
250258
var allValid = true;
251-
utils.each(utils.toStringArray(list), function (name) {
259+
utils.each(utils.toValidRoleArray(list), function (name) {
252260
if (!utils.validName(name, throwOnInvalid)) {
253261
allValid = false;
254262
return false; // break out of loop
@@ -281,7 +289,7 @@ var utils = {
281289
throw new core_1.AccessControlError("Invalid action possession: \"" + action + "\"");
282290
}
283291
var perms = o[action];
284-
if (!utils.isEmptyArray(perms) && !utils.isFilledStringArray(perms)) {
292+
if (!utils.isEmptyArray(perms) && !utils.isFilledValidRoleArray(perms)) {
285293
throw new core_1.AccessControlError("Invalid resource attributes for action \"" + action + "\".");
286294
}
287295
});
@@ -307,7 +315,7 @@ var utils = {
307315
if (!utils.validName(resourceName, false)) {
308316
if (resourceName === '$extend') {
309317
var extRoles = role[resourceName]; // semantics
310-
if (!utils.isFilledStringArray(extRoles)) {
318+
if (!utils.isFilledValidRoleArray(extRoles)) {
311319
throw new core_1.AccessControlError("Invalid extend value for role \"" + roleName + "\": " + JSON.stringify(extRoles));
312320
}
313321
else {
@@ -344,7 +352,7 @@ var utils = {
344352
var type = utils.type(o);
345353
if (type === 'object') {
346354
utils.eachKey(o, function (roleName) {
347-
if (utils.validName(roleName)) {
355+
if (utils.validName(roleName)) { // throws on failure
348356
return utils.validRoleObject(o, roleName); // throws on failure
349357
}
350358
/* istanbul ignore next */
@@ -436,8 +444,8 @@ var utils = {
436444
// clone the object
437445
query = Object.assign({}, query);
438446
// validate and normalize role(s)
439-
query.role = utils.toStringArray(query.role);
440-
if (!utils.isFilledStringArray(query.role)) {
447+
query.role = utils.toValidRoleArray(query.role);
448+
if (!utils.isFilledValidRoleArray(query.role)) {
441449
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(query.role));
442450
}
443451
// validate resource
@@ -467,13 +475,13 @@ var utils = {
467475
// clone the object
468476
access = Object.assign({}, access);
469477
// validate and normalize role(s)
470-
access.role = utils.toStringArray(access.role);
471-
if (access.role.length === 0 || !utils.isFilledStringArray(access.role)) {
478+
access.role = utils.toValidRoleArray(access.role);
479+
if (access.role.length === 0 || !utils.isFilledValidRoleArray(access.role)) {
472480
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(access.role));
473481
}
474482
// validate and normalize resource
475-
access.resource = utils.toStringArray(access.resource);
476-
if (access.resource.length === 0 || !utils.isFilledStringArray(access.resource)) {
483+
access.resource = utils.toValidRoleArray(access.resource);
484+
if (access.resource.length === 0 || !utils.isFilledValidRoleArray(access.resource)) {
477485
throw new core_1.AccessControlError("Invalid resource(s): " + JSON.stringify(access.resource));
478486
}
479487
// normalize attributes
@@ -482,7 +490,7 @@ var utils = {
482490
}
483491
else {
484492
// if omitted and not denied, all attributes are allowed
485-
access.attributes = !access.attributes ? ['*'] : utils.toStringArray(access.attributes);
493+
access.attributes = !access.attributes ? ['*'] : utils.toValidRoleArray(access.attributes);
486494
}
487495
// this part is not necessary if this is invoked from a comitter method
488496
// such as `createAny()`. So we'll check if we need to validate all
@@ -545,7 +553,7 @@ var utils = {
545553
* Gets roles and extended roles in a flat array.
546554
*/
547555
getFlatRoles: function (grants, roles) {
548-
var arrRoles = utils.toStringArray(roles);
556+
var arrRoles = utils.toValidRoleArray(roles);
549557
if (arrRoles.length === 0) {
550558
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles));
551559
}
@@ -561,7 +569,7 @@ var utils = {
561569
* from the given roles.
562570
* @param {Any} grants - Grants model to be checked.
563571
* @param {string[]} roles - Roles to be checked.
564-
* @returns {string[]} - Array of non-existent roles. Empty array if
572+
* @returns {ValidRole[]} - Array of non-existent roles. Empty array if
565573
* all exist.
566574
*/
567575
getNonExistentRoles: function (grants, roles) {
@@ -590,7 +598,7 @@ var utils = {
590598
* if none.
591599
*/
592600
getCrossExtendingRole: function (grants, roleName, extenderRoles) {
593-
var extenders = utils.toStringArray(extenderRoles);
601+
var extenders = utils.toValidRoleArray(extenderRoles);
594602
var crossInherited = null;
595603
utils.each(extenders, function (e) {
596604
if (crossInherited || roleName === e) {
@@ -628,14 +636,14 @@ var utils = {
628636
*/
629637
extendRole: function (grants, roles, extenderRoles) {
630638
// roles cannot be omitted or an empty array
631-
roles = utils.toStringArray(roles);
639+
roles = utils.toValidRoleArray(roles);
632640
if (roles.length === 0) {
633641
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles));
634642
}
635643
// extenderRoles cannot be omitted or but can be an empty array
636644
if (utils.isEmptyArray(extenderRoles))
637645
return;
638-
var arrExtRoles = utils.toStringArray(extenderRoles).concat();
646+
var arrExtRoles = utils.toValidRoleArray(extenderRoles).concat();
639647
if (arrExtRoles.length === 0) {
640648
throw new core_1.AccessControlError("Cannot inherit invalid role(s): " + JSON.stringify(extenderRoles));
641649
}
@@ -675,7 +683,7 @@ var utils = {
675683
*/
676684
preCreateRoles: function (grants, roles) {
677685
if (typeof roles === 'string')
678-
roles = utils.toStringArray(roles);
686+
roles = utils.toValidRoleArray(roles);
679687
if (!Array.isArray(roles) || roles.length === 0) {
680688
throw new core_1.AccessControlError("Invalid role(s): " + JSON.stringify(roles));
681689
}
@@ -714,7 +722,7 @@ var utils = {
714722
// If possession (in action value or as a separate property) is
715723
// omitted, it will default to "any". e.g. "create" —>
716724
// "create:any"
717-
grantItem[res][ap] = utils.toStringArray(access.attributes);
725+
grantItem[res][ap] = utils.toValidRoleArray(access.attributes);
718726
});
719727
});
720728
},

‎package-lock.json

+5,671-3,737
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"deps": "npm-check -u && snyk test",
1919
"clean": "rimraf ./lib",
2020
"build": "npm run clean && mkdirp ./lib && tsc",
21+
"watch": "npm run build -- -w",
2122
"test": "npm run build && jest --verbose --no-cache",
2223
"test!": "jest --verbose --no-cache",
2324
"cover": "npm run build && jest --coverage --verbose --no-cache",
@@ -108,7 +109,7 @@
108109
"rimraf": "^2.6.3",
109110
"snyk": "^1.122.0",
110111
"ts-jest": "^22.0.4",
111-
"typescript": "^2.7.2"
112+
"typescript": "4.1.2"
112113
},
113114
"dependencies": {
114115
"notation": "^1.3.6"

‎src/AccessControl.ts

+26-24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Access, IAccessInfo, Query, IQueryInfo, Permission, AccessControlError
22
import { Action, Possession, actions, possessions } from './enums';
33
import { utils, ERR_LOCK } from './utils';
44

5+
import type { ValidRoleOrArray, ValidRole } from '.';
6+
57
/**
68
* @classdesc
79
* AccessControl class that implements RBAC (Role-Based Access Control) basics
@@ -273,7 +275,7 @@ class AccessControl {
273275
* @throws {AccessControlError} - If a role is extended by itself or a
274276
* non-existent role. Or if called after `.lock()` is called.
275277
*/
276-
extendRole(roles: string | string[], extenderRoles: string | string[]): AccessControl {
278+
extendRole(roles: ValidRoleOrArray, extenderRoles: ValidRoleOrArray): AccessControl {
277279
if (this.isLocked) throw new AccessControlError(ERR_LOCK);
278280
utils.extendRole(this._grants, roles, extenderRoles);
279281
return this;
@@ -290,11 +292,11 @@ class AccessControl {
290292
*
291293
* @throws {AccessControlError} - If called after `.lock()` is called.
292294
*/
293-
removeRoles(roles: string | string[]): AccessControl {
295+
removeRoles(roles: ValidRoleOrArray): AccessControl {
294296
if (this.isLocked) throw new AccessControlError(ERR_LOCK);
295297

296-
let rolesToRemove: string[] = utils.toStringArray(roles);
297-
if (rolesToRemove.length === 0 || !utils.isFilledStringArray(rolesToRemove)) {
298+
let rolesToRemove: ValidRole[] = utils.toValidRoleArray(roles);
299+
if (rolesToRemove.length === 0 || !utils.isFilledValidRoleArray(rolesToRemove)) {
298300
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(roles)}`);
299301
}
300302
rolesToRemove.forEach((roleName: string) => {
@@ -328,7 +330,7 @@ class AccessControl {
328330
*
329331
* @throws {AccessControlError} - If called after `.lock()` is called.
330332
*/
331-
removeResources(resources: string | string[], roles?: string | string[]): AccessControl {
333+
removeResources(resources: ValidRoleOrArray, roles?: ValidRoleOrArray): AccessControl {
332334
if (this.isLocked) throw new AccessControlError(ERR_LOCK);
333335

334336
// _removePermission has a third argument `actionPossession`. if
@@ -360,8 +362,8 @@ class AccessControl {
360362
*
361363
* @returns {Array<String>}
362364
*/
363-
getInheritedRolesOf(role: string): string[] {
364-
let roles: string[] = utils.getRoleHierarchyOf(this._grants, role);
365+
getInheritedRolesOf(role: ValidRole): ValidRole[] {
366+
let roles: ValidRole[] = utils.getRoleHierarchyOf(this._grants, role);
365367
roles.shift();
366368
return roles;
367369
}
@@ -370,7 +372,7 @@ class AccessControl {
370372
* Alias of `getInheritedRolesOf`
371373
* @private
372374
*/
373-
getExtendedRolesOf(role: string): string[] {
375+
getExtendedRolesOf(role: ValidRole): ValidRole[] {
374376
return this.getInheritedRolesOf(role);
375377
}
376378

@@ -392,7 +394,7 @@ class AccessControl {
392394
*
393395
* @returns {Boolean}
394396
*/
395-
hasRole(role: string | string[]): boolean {
397+
hasRole(role: ValidRoleOrArray): boolean {
396398
if (Array.isArray(role)) {
397399
return role.every((item: string) => this._grants.hasOwnProperty(item));
398400
}
@@ -407,7 +409,7 @@ class AccessControl {
407409
*
408410
* @returns {Boolean}
409411
*/
410-
hasResource(resource: string | string[]): boolean {
412+
hasResource(resource: ValidRoleOrArray): boolean {
411413
let resources = this.getResources();
412414
if (Array.isArray(resource)) {
413415
return resource.every((item: string) => resources.indexOf(item) >= 0);
@@ -448,7 +450,7 @@ class AccessControl {
448450
* ac.can(['admin', 'user']).createOwn('profile');
449451
* // Note: when multiple roles checked, acquired attributes are unioned (merged).
450452
*/
451-
can(role: string | string[] | IQueryInfo): Query {
453+
can(role: ValidRoleOrArray | IQueryInfo): Query {
452454
// throw on explicit undefined
453455
if (arguments.length !== 0 && role === undefined) {
454456
throw new AccessControlError('Invalid role(s): undefined');
@@ -461,7 +463,7 @@ class AccessControl {
461463
* Alias of `can()`.
462464
* @private
463465
*/
464-
query(role: string | string[] | IQueryInfo): Query {
466+
query(role: ValidRoleOrArray | IQueryInfo): Query {
465467
return this.can(role);
466468
}
467469

@@ -552,7 +554,7 @@ class AccessControl {
552554
* // Note: when attributes is omitted, it will default to `['*']`
553555
* // which means all attributes (of the resource) are allowed.
554556
*/
555-
grant(role?: string | string[] | IAccessInfo): Access {
557+
grant(role?: ValidRoleOrArray | IAccessInfo): Access {
556558
if (this.isLocked) throw new AccessControlError(ERR_LOCK);
557559
// throw on explicit undefined
558560
if (arguments.length !== 0 && role === undefined) {
@@ -566,7 +568,7 @@ class AccessControl {
566568
* Alias of `grant()`.
567569
* @private
568570
*/
569-
allow(role?: string | string[] | IAccessInfo): Access {
571+
allow(role?: ValidRoleOrArray | IAccessInfo): Access {
570572
return this.grant(role);
571573
}
572574

@@ -622,7 +624,7 @@ class AccessControl {
622624
* // To deny same resource for multiple roles:
623625
* ac.deny(['admin', 'user']).createOwn('profile');
624626
*/
625-
deny(role?: string | string[] | IAccessInfo): Access {
627+
deny(role?: ValidRoleOrArray | IAccessInfo): Access {
626628
if (this.isLocked) throw new AccessControlError(ERR_LOCK);
627629
// throw on explicit undefined
628630
if (arguments.length !== 0 && role === undefined) {
@@ -636,7 +638,7 @@ class AccessControl {
636638
* Alias of `deny()`.
637639
* @private
638640
*/
639-
reject(role?: string | string[] | IAccessInfo): Access {
641+
reject(role?: ValidRoleOrArray | IAccessInfo): Access {
640642
return this.deny(role);
641643
}
642644

@@ -647,25 +649,25 @@ class AccessControl {
647649
/**
648650
* @private
649651
*/
650-
_removePermission(resources: string | string[], roles?: string | string[], actionPossession?: string) {
651-
resources = utils.toStringArray(resources);
652+
_removePermission(resources: ValidRoleOrArray, roles?: ValidRoleOrArray, actionPossession?: string) {
653+
resources = utils.toValidRoleArray(resources);
652654
// resources is set but returns empty array.
653-
if (resources.length === 0 || !utils.isFilledStringArray(resources)) {
655+
if (resources.length === 0 || !utils.isFilledValidRoleArray(resources)) {
654656
throw new AccessControlError(`Invalid resource(s): ${JSON.stringify(resources)}`);
655657
}
656658

657659
if (roles !== undefined) {
658-
roles = utils.toStringArray(roles);
660+
roles = utils.toValidRoleArray(roles);
659661
// roles is set but returns empty array.
660-
if (roles.length === 0 || !utils.isFilledStringArray(roles)) {
662+
if (roles.length === 0 || !utils.isFilledValidRoleArray(roles)) {
661663
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(roles)}`);
662664
}
663665
}
664-
utils.eachRoleResource(this._grants, (role: string, resource: string, permissions: any) => {
665-
if (resources.indexOf(resource) >= 0
666+
utils.eachRoleResource(this._grants, (role: string, resource: string | number, permissions: any) => {
667+
if ((resources as ValidRole[]).indexOf(resource) >= 0
666668
// roles is optional. so remove if role is not defined.
667669
// if defined, check if the current role is in the list.
668-
&& (!roles || roles.indexOf(role) >= 0)) {
670+
&& (!roles || (roles as ValidRole[]).indexOf(role) >= 0)) {
669671
if (actionPossession) {
670672
// e.g. 'create' » 'create:any'
671673
// to parse and normalize actionPossession string:

‎src/core/Access.ts

+27-23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { IAccessInfo, AccessControlError } from '../core';
33
import { Action, Possession, actions, possessions } from '../enums';
44
import { utils } from '../utils';
55

6+
export type ValidRole = string | number
7+
export type ValidRoleOrArray = ValidRole | ValidRole[]
8+
9+
610
/**
711
* Represents the inner `Access` class that helps build an access information
812
* to be granted or denied; and finally commits it to the underlying grants
@@ -51,12 +55,12 @@ class Access {
5155
* @param {Boolean} denied
5256
* Specifies whether this `Access` is denied.
5357
*/
54-
constructor(ac: AccessControl, roleOrInfo?: string | string[] | IAccessInfo, denied: boolean = false) {
58+
constructor(ac: AccessControl, roleOrInfo?: ValidRoleOrArray | IAccessInfo, denied: boolean = false) {
5559
this._ac = ac;
5660
this._grants = (ac as any)._grants;
5761
this._.denied = denied;
5862

59-
if (typeof roleOrInfo === 'string' || Array.isArray(roleOrInfo)) {
63+
if (typeof roleOrInfo === 'string' || typeof roleOrInfo === 'number' || Array.isArray(roleOrInfo)) {
6064
this.role(roleOrInfo);
6165
} else if (utils.type(roleOrInfo) === 'object') {
6266
if (Object.keys(roleOrInfo).length === 0) {
@@ -99,7 +103,7 @@ class Access {
99103
* @returns {Access}
100104
* Self instance of `Access`.
101105
*/
102-
role(value: string | string[]): Access {
106+
role(value: ValidRoleOrArray): Access {
103107
// in case chain is not terminated (e.g. `ac.grant('user')`) we'll
104108
// create/commit the roles to grants with an empty object.
105109
utils.preCreateRoles(this._grants, value);
@@ -115,7 +119,7 @@ class Access {
115119
* @returns {Access}
116120
* Self instance of `Access`.
117121
*/
118-
resource(value: string | string[]): Access {
122+
resource(value: ValidRoleOrArray): Access {
119123
// this will throw if any item fails
120124
utils.hasValidNames(value, true);
121125
this._.resource = value;
@@ -129,7 +133,7 @@ class Access {
129133
* @returns {Access}
130134
* Self instance of `Access`.
131135
*/
132-
attributes(value: string | string[]): Access {
136+
attributes(value: ValidRoleOrArray): Access {
133137
this._.attributes = value;
134138
return this;
135139
}
@@ -151,7 +155,7 @@ class Access {
151155
* const permission = ac.can('admin').createAny('video');
152156
* console.log(permission.granted); // true
153157
*/
154-
extend(roles: string | string[]): Access {
158+
extend(roles: ValidRoleOrArray): Access {
155159
utils.extendRole(this._grants, this._.role, roles);
156160
return this;
157161
}
@@ -160,7 +164,7 @@ class Access {
160164
* Alias of `extend`.
161165
* @private
162166
*/
163-
inherit(roles: string | string[]): Access {
167+
inherit(roles: ValidRoleOrArray): Access {
164168
this.extend(roles);
165169
return this;
166170
}
@@ -180,7 +184,7 @@ class Access {
180184
* ac.grant('user').createOwn('video')
181185
* .grant('admin').updateAny('video');
182186
*/
183-
grant(roleOrInfo?: string | string[] | IAccessInfo): Access {
187+
grant(roleOrInfo?: ValidRoleOrArray | IAccessInfo): Access {
184188
return (new Access(this._ac, roleOrInfo, false)).attributes(['*']);
185189
}
186190

@@ -199,7 +203,7 @@ class Access {
199203
* ac.grant('admin').createAny('video')
200204
* .deny('user').deleteAny('video');
201205
*/
202-
deny(roleOrInfo?: string | string[] | IAccessInfo): Access {
206+
deny(roleOrInfo?: ValidRoleOrArray | IAccessInfo): Access {
203207
return (new Access(this._ac, roleOrInfo, true)).attributes([]);
204208
}
205209

@@ -235,7 +239,7 @@ class Access {
235239
* Self instance of `Access` so that you can chain and define
236240
* another access instance to be committed.
237241
*/
238-
createOwn(resource?: string | string[], attributes?: string | string[]): Access {
242+
createOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
239243
return this._prepareAndCommit(Action.CREATE, Possession.OWN, resource, attributes);
240244
}
241245

@@ -264,14 +268,14 @@ class Access {
264268
* Self instance of `Access` so that you can chain and define
265269
* another access instance to be committed.
266270
*/
267-
createAny(resource?: string | string[], attributes?: string | string[]): Access {
271+
createAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
268272
return this._prepareAndCommit(Action.CREATE, Possession.ANY, resource, attributes);
269273
}
270274
/**
271275
* Alias of `createAny`
272276
* @private
273277
*/
274-
create(resource?: string | string[], attributes?: string | string[]): Access {
278+
create(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
275279
return this.createAny(resource, attributes);
276280
}
277281

@@ -297,7 +301,7 @@ class Access {
297301
* Self instance of `Access` so that you can chain and define
298302
* another access instance to be committed.
299303
*/
300-
readOwn(resource?: string | string[], attributes?: string | string[]): Access {
304+
readOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
301305
return this._prepareAndCommit(Action.READ, Possession.OWN, resource, attributes);
302306
}
303307

@@ -326,14 +330,14 @@ class Access {
326330
* Self instance of `Access` so that you can chain and define
327331
* another access instance to be committed.
328332
*/
329-
readAny(resource?: string | string[], attributes?: string | string[]): Access {
333+
readAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
330334
return this._prepareAndCommit(Action.READ, Possession.ANY, resource, attributes);
331335
}
332336
/**
333337
* Alias of `readAny`
334338
* @private
335339
*/
336-
read(resource?: string | string[], attributes?: string | string[]): Access {
340+
read(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
337341
return this.readAny(resource, attributes);
338342
}
339343

@@ -359,7 +363,7 @@ class Access {
359363
* Self instance of `Access` so that you can chain and define
360364
* another access instance to be committed.
361365
*/
362-
updateOwn(resource?: string | string[], attributes?: string | string[]): Access {
366+
updateOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
363367
return this._prepareAndCommit(Action.UPDATE, Possession.OWN, resource, attributes);
364368
}
365369

@@ -388,14 +392,14 @@ class Access {
388392
* Self instance of `Access` so that you can chain and define
389393
* another access instance to be committed.
390394
*/
391-
updateAny(resource?: string | string[], attributes?: string | string[]): Access {
395+
updateAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
392396
return this._prepareAndCommit(Action.UPDATE, Possession.ANY, resource, attributes);
393397
}
394398
/**
395399
* Alias of `updateAny`
396400
* @private
397401
*/
398-
update(resource?: string | string[], attributes?: string | string[]): Access {
402+
update(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
399403
return this.updateAny(resource, attributes);
400404
}
401405

@@ -421,7 +425,7 @@ class Access {
421425
* Self instance of `Access` so that you can chain and define
422426
* another access instance to be committed.
423427
*/
424-
deleteOwn(resource?: string | string[], attributes?: string | string[]): Access {
428+
deleteOwn(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
425429
return this._prepareAndCommit(Action.DELETE, Possession.OWN, resource, attributes);
426430
}
427431

@@ -450,14 +454,14 @@ class Access {
450454
* Self instance of `Access` so that you can chain and define
451455
* another access instance to be committed.
452456
*/
453-
deleteAny(resource?: string | string[], attributes?: string | string[]): Access {
457+
deleteAny(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
454458
return this._prepareAndCommit(Action.DELETE, Possession.ANY, resource, attributes);
455459
}
456460
/**
457461
* Alias of `deleteAny`
458462
* @private
459463
*/
460-
delete(resource?: string | string[], attributes?: string | string[]): Access {
464+
delete(resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
461465
return this.deleteAny(resource, attributes);
462466
}
463467

@@ -474,7 +478,7 @@ class Access {
474478
* @returns {Access}
475479
* Self instance of `Access`.
476480
*/
477-
private _prepareAndCommit(action: string, possession: string, resource?: string | string[], attributes?: string | string[]): Access {
481+
private _prepareAndCommit(action: string, possession: string, resource?: ValidRoleOrArray, attributes?: ValidRoleOrArray): Access {
478482
this._.action = action;
479483
this._.possession = possession;
480484
if (resource) this._.resource = resource;
@@ -483,7 +487,7 @@ class Access {
483487
this._.attributes = [];
484488
} else {
485489
// if omitted and not denied, all attributes are allowed
486-
this._.attributes = attributes ? utils.toStringArray(attributes) : ['*'];
490+
this._.attributes = attributes ? utils.toValidRoleArray(attributes) : ['*'];
487491
}
488492

489493
utils.commitToGrants(this._grants, this._, false);

‎src/core/IAccessInfo.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ValidRoleOrArray } from '.';
12
/**
23
* An interface that defines an access information to be granted or denied.
34
* When you start a method chain with `AccessControl#grant` or `AccessControl#deny`
@@ -10,19 +11,19 @@ interface IAccessInfo {
1011
* Indicates a single or multiple roles for this access information.
1112
* @type {String|Array<String>}
1213
*/
13-
role?: string | string[];
14+
role?: ValidRoleOrArray;
1415
/**
1516
* Indicates a single or multiple target resources for this access
1617
* information.
1718
* @type {String|Array<String>}
1819
*/
19-
resource?: string | string[];
20+
resource?: ValidRoleOrArray;
2021
/**
2122
* Defines the resource attributes which are granted. If denied, this will
2223
* default to an empty array.
2324
* @type {String|Array<String>}
2425
*/
25-
attributes?: string | string[];
26+
attributes?: ValidRoleOrArray;
2627
/**
2728
* Defines the type of the operation that is (or not) to be performed on
2829
* the resource(s) by the defined role(s).

‎src/core/IQueryInfo.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ValidRoleOrArray } from '.';
2+
13
/**
24
* An interface that defines an access information to be queried.
35
* When you start a method chain with `AccessControl#can` method, you're
@@ -10,7 +12,7 @@ interface IQueryInfo {
1012
* Indicates a single or multiple roles to be queried.
1113
* @type {String|Array<String>}
1214
*/
13-
role?: string | string[];
15+
role?: ValidRoleOrArray;
1416
/**
1517
* Indicates the resource to be queried.
1618
* @type {String}

‎src/core/Query.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IQueryInfo, Permission, AccessControlError } from '../core';
22
import { Action, Possession } from '../enums';
33
import { utils } from '../utils';
4+
import type { ValidRoleOrArray } from '.';
45

56
/**
67
* Represents the inner `Query` class that helps build an access information
@@ -38,7 +39,7 @@ class Query {
3839
* Either a single or array of roles or an
3940
* {@link ?api=ac#AccessControl~IQueryInfo|`IQueryInfo` arbitrary object}.
4041
*/
41-
constructor(grants: any, roleOrInfo?: string | string[] | IQueryInfo) {
42+
constructor(grants: any, roleOrInfo?: ValidRoleOrArray | IQueryInfo) {
4243
this._grants = grants;
4344

4445
if (typeof roleOrInfo === 'string' || Array.isArray(roleOrInfo)) {
@@ -70,7 +71,7 @@ class Query {
7071
* @returns {Query}
7172
* Self instance of `Query`.
7273
*/
73-
role(role: string | string[]): Query {
74+
role(role: ValidRoleOrArray): Query {
7475
this._.role = role;
7576
return this;
7677
}

‎src/utils.ts

+51-43
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { AccessControl } from './';
55
import { Action, actions, Possession, possessions } from './enums';
66
import { IAccessInfo, IQueryInfo, AccessControlError } from './core';
77

8+
import type { ValidRole, ValidRoleOrArray } from '.';
9+
810
/**
911
* List of reserved keywords.
1012
* i.e. Roles, resources with these names are not allowed.
@@ -64,9 +66,10 @@ const utils = {
6466
* @param {Any} value
6567
* @returns {string[]}
6668
*/
67-
toStringArray(value: any): string[] {
69+
toValidRoleArray(value: unknown): ValidRole[] {
6870
if (Array.isArray(value)) return value;
6971
if (typeof value === 'string') return value.trim().split(/\s*[;,]\s*/);
72+
if (typeof value === 'number') return [value]
7073
// throw new Error('Expected a string or array of strings, got ' + utils.type(value));
7174
return [];
7275
},
@@ -77,12 +80,17 @@ const utils = {
7780
* @param {Array} arr - Array to be checked.
7881
* @returns {Boolean}
7982
*/
80-
isFilledStringArray(arr: any[]): boolean {
83+
isFilledValidRoleArray(arr: unknown[]): arr is ValidRole[] {
8184
if (!arr || !Array.isArray(arr)) return false;
82-
for (let s of arr) {
83-
if (typeof s !== 'string' || s.trim() === '') return false;
85+
for (let e of arr) {
86+
if (typeof e !== 'number' && (typeof e !== 'string' || e.trim() === '')) return false;
8487
}
85-
return true;
88+
return arr.every(this.isValidRole)
89+
},
90+
91+
isValidRole(e: unknown): e is ValidRole {
92+
if (typeof e !== 'number' && (typeof e !== 'string' || e.trim() === '')) return false;
93+
return true;
8694
},
8795

8896
/**
@@ -100,7 +108,7 @@ const utils = {
100108
* @param {Any} item - Item to be pushed to array.
101109
* @returns {Array}
102110
*/
103-
pushUniq(arr: string[], item: string): string[] {
111+
pushUniq(arr: ValidRole[], item: ValidRole): ValidRole[] {
104112
if (arr.indexOf(item) < 0) arr.push(item);
105113
return arr;
106114
},
@@ -111,9 +119,9 @@ const utils = {
111119
* @param {Array} arrB
112120
* @returns {Array} - Concat'ed array.
113121
*/
114-
uniqConcat(arrA: string[], arrB: string[]): string[] {
115-
const arr: string[] = arrA.concat();
116-
arrB.forEach((b: string) => {
122+
uniqConcat(arrA: ValidRole[], arrB: ValidRole[]): ValidRole[] {
123+
const arr = arrA.concat();
124+
arrB.forEach((b) => {
117125
utils.pushUniq(arr, b);
118126
});
119127
return arr;
@@ -125,7 +133,7 @@ const utils = {
125133
* @param {Array} arrB
126134
* @return {Array} - Resulting array.
127135
*/
128-
subtractArray(arrA: string[], arrB: string[]): string[] {
136+
subtractArray(arrA: ValidRole[], arrB: ValidRole[]): ValidRole[] {
129137
return arrA.concat().filter(a => arrB.indexOf(a) === -1);
130138
},
131139

@@ -229,12 +237,12 @@ const utils = {
229237
* @throws {AccessControlError} - If `throwOnInvalid` is enabled and name
230238
* is invalid.
231239
*/
232-
validName(name: string, throwOnInvalid: boolean = true): boolean {
233-
if (typeof name !== 'string' || name.trim() === '') {
240+
validName(name: ValidRole, throwOnInvalid: boolean = true): boolean {
241+
if (!this.isValidRole(name)) {
234242
if (!throwOnInvalid) return false;
235243
throw new AccessControlError('Invalid name, expected a valid string.');
236244
}
237-
if (RESERVED_KEYWORDS.indexOf(name) >= 0) {
245+
if (RESERVED_KEYWORDS.indexOf(name as string) >= 0) {
238246
if (!throwOnInvalid) return false;
239247
throw new AccessControlError(`Cannot use reserved name: "${name}"`);
240248
}
@@ -255,7 +263,7 @@ const utils = {
255263
*/
256264
hasValidNames(list: any, throwOnInvalid: boolean = true): boolean {
257265
let allValid = true;
258-
utils.each(utils.toStringArray(list), name => {
266+
utils.each(utils.toValidRoleArray(list), name => {
259267
if (!utils.validName(name, throwOnInvalid)) {
260268
allValid = false;
261269
return false; // break out of loop
@@ -290,7 +298,7 @@ const utils = {
290298
throw new AccessControlError(`Invalid action possession: "${action}"`);
291299
}
292300
let perms = o[action];
293-
if (!utils.isEmptyArray(perms) && !utils.isFilledStringArray(perms)) {
301+
if (!utils.isEmptyArray(perms) && !utils.isFilledValidRoleArray(perms)) {
294302
throw new AccessControlError(`Invalid resource attributes for action "${action}".`);
295303
}
296304
});
@@ -318,7 +326,7 @@ const utils = {
318326
if (!utils.validName(resourceName, false)) {
319327
if (resourceName === '$extend') {
320328
let extRoles: string[] = role[resourceName]; // semantics
321-
if (!utils.isFilledStringArray(extRoles)) {
329+
if (!utils.isFilledValidRoleArray(extRoles)) {
322330
throw new AccessControlError(`Invalid extend value for role "${roleName}": ${JSON.stringify(extRoles)}`);
323331
} else {
324332
// attempt to actually extend the roles. this will throw
@@ -449,8 +457,8 @@ const utils = {
449457
// clone the object
450458
query = Object.assign({}, query);
451459
// validate and normalize role(s)
452-
query.role = utils.toStringArray(query.role);
453-
if (!utils.isFilledStringArray(query.role)) {
460+
query.role = utils.toValidRoleArray(query.role);
461+
if (!utils.isFilledValidRoleArray(query.role)) {
454462
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(query.role)}`);
455463
}
456464

@@ -482,14 +490,14 @@ const utils = {
482490
// clone the object
483491
access = Object.assign({}, access);
484492
// validate and normalize role(s)
485-
access.role = utils.toStringArray(access.role);
486-
if (access.role.length === 0 || !utils.isFilledStringArray(access.role)) {
493+
access.role = utils.toValidRoleArray(access.role);
494+
if (access.role.length === 0 || !utils.isFilledValidRoleArray(access.role)) {
487495
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(access.role)}`);
488496
}
489497

490498
// validate and normalize resource
491-
access.resource = utils.toStringArray(access.resource);
492-
if (access.resource.length === 0 || !utils.isFilledStringArray(access.resource)) {
499+
access.resource = utils.toValidRoleArray(access.resource);
500+
if (access.resource.length === 0 || !utils.isFilledValidRoleArray(access.resource)) {
493501
throw new AccessControlError(`Invalid resource(s): ${JSON.stringify(access.resource)}`);
494502
}
495503

@@ -498,7 +506,7 @@ const utils = {
498506
access.attributes = [];
499507
} else {
500508
// if omitted and not denied, all attributes are allowed
501-
access.attributes = !access.attributes ? ['*'] : utils.toStringArray(access.attributes);
509+
access.attributes = !access.attributes ? ['*'] : utils.toValidRoleArray(access.attributes);
502510
}
503511

504512
// this part is not necessary if this is invoked from a comitter method
@@ -533,15 +541,15 @@ const utils = {
533541
* @param {string} roleName - Role name to be inspected.
534542
* @returns {string[]}
535543
*/
536-
getRoleHierarchyOf(grants: any, roleName: string, rootRole?: string): string[] {
544+
getRoleHierarchyOf(grants: any, roleName: ValidRole, rootRole?: ValidRole): ValidRole[] {
537545
// `rootRole` is for memory storage. Do NOT set it when using;
538546
// and do NOT document this paramter.
539547
// rootRole = rootRole || roleName;
540548

541549
const role: any = grants[roleName];
542550
if (!role) throw new AccessControlError(`Role not found: "${roleName}"`);
543551

544-
let arr: string[] = [roleName];
552+
let arr = [roleName];
545553
if (!Array.isArray(role.$extend) || role.$extend.length === 0) return arr;
546554

547555
role.$extend.forEach((exRoleName: string) => {
@@ -556,7 +564,7 @@ const utils = {
556564
if (rootRole && (rootRole === exRoleName)) {
557565
throw new AccessControlError(`Cross inheritance is not allowed. Role "${exRoleName}" already extends "${rootRole}".`);
558566
}
559-
let ext: string[] = utils.getRoleHierarchyOf(grants, exRoleName, rootRole || roleName);
567+
let ext = utils.getRoleHierarchyOf(grants, exRoleName, rootRole || roleName);
560568
arr = utils.uniqConcat(arr, ext);
561569
});
562570
return arr;
@@ -565,12 +573,12 @@ const utils = {
565573
/**
566574
* Gets roles and extended roles in a flat array.
567575
*/
568-
getFlatRoles(grants: any, roles: string | string[]): string[] {
569-
const arrRoles: string[] = utils.toStringArray(roles);
576+
getFlatRoles(grants: any, roles: ValidRoleOrArray): ValidRole[] {
577+
const arrRoles = utils.toValidRoleArray(roles);
570578
if (arrRoles.length === 0) {
571579
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(roles)}`);
572580
}
573-
let arr: string[] = utils.uniqConcat([], arrRoles); // roles.concat();
581+
let arr = utils.uniqConcat([], arrRoles); // roles.concat();
574582
arrRoles.forEach((roleName: string) => {
575583
arr = utils.uniqConcat(arr, utils.getRoleHierarchyOf(grants, roleName));
576584
});
@@ -583,11 +591,11 @@ const utils = {
583591
* from the given roles.
584592
* @param {Any} grants - Grants model to be checked.
585593
* @param {string[]} roles - Roles to be checked.
586-
* @returns {string[]} - Array of non-existent roles. Empty array if
594+
* @returns {ValidRole[]} - Array of non-existent roles. Empty array if
587595
* all exist.
588596
*/
589-
getNonExistentRoles(grants: any, roles: string[]) {
590-
let non: string[] = [];
597+
getNonExistentRoles(grants: any, roles: ValidRole[]) {
598+
let non: ValidRole[] = [];
591599
if (utils.isEmptyArray(roles)) return non;
592600
for (let role of roles) {
593601
if (!grants.hasOwnProperty(role)) non.push(role);
@@ -609,8 +617,8 @@ const utils = {
609617
* @returns {string|null} - Returns the first cross extending role. `null`
610618
* if none.
611619
*/
612-
getCrossExtendingRole(grants: any, roleName: string, extenderRoles: string | string[]): string {
613-
const extenders: string[] = utils.toStringArray(extenderRoles);
620+
getCrossExtendingRole(grants: any, roleName: string, extenderRoles: ValidRoleOrArray): string {
621+
const extenders: ValidRole[] = utils.toValidRoleArray(extenderRoles);
614622
let crossInherited: any = null;
615623
utils.each(extenders, (e: string) => {
616624
if (crossInherited || roleName === e) {
@@ -647,22 +655,22 @@ const utils = {
647655
* @throws {Error} If a role is extended by itself, a non-existent role or
648656
* a cross-inherited role.
649657
*/
650-
extendRole(grants: any, roles: string | string[], extenderRoles: string | string[]) {
658+
extendRole(grants: any, roles: ValidRoleOrArray, extenderRoles: ValidRoleOrArray) {
651659
// roles cannot be omitted or an empty array
652-
roles = utils.toStringArray(roles);
660+
roles = utils.toValidRoleArray(roles);
653661
if (roles.length === 0) {
654662
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(roles)}`);
655663
}
656664

657665
// extenderRoles cannot be omitted or but can be an empty array
658666
if (utils.isEmptyArray(extenderRoles)) return;
659667

660-
const arrExtRoles: string[] = utils.toStringArray(extenderRoles).concat();
668+
const arrExtRoles: ValidRole[] = utils.toValidRoleArray(extenderRoles).concat();
661669
if (arrExtRoles.length === 0) {
662670
throw new AccessControlError(`Cannot inherit invalid role(s): ${JSON.stringify(extenderRoles)}`);
663671
}
664672

665-
const nonExistentExtRoles: string[] = utils.getNonExistentRoles(grants, arrExtRoles);
673+
const nonExistentExtRoles: ValidRole[] = utils.getNonExistentRoles(grants, arrExtRoles);
666674
if (nonExistentExtRoles.length > 0) {
667675
throw new AccessControlError(`Cannot inherit non-existent role(s): "${nonExistentExtRoles.join(', ')}"`);
668676
}
@@ -699,12 +707,12 @@ const utils = {
699707
* @param {Any} grants
700708
* @param {string|string[]} roles
701709
*/
702-
preCreateRoles(grants: any, roles: string | string[]) {
703-
if (typeof roles === 'string') roles = utils.toStringArray(roles);
710+
preCreateRoles(grants: any, roles: ValidRoleOrArray) {
711+
if (typeof roles === 'string') roles = utils.toValidRoleArray(roles);
704712
if (!Array.isArray(roles) || roles.length === 0) {
705713
throw new AccessControlError(`Invalid role(s): ${JSON.stringify(roles)}`);
706714
}
707-
(roles as string[]).forEach((role: string) => {
715+
roles.forEach((role: string) => {
708716
if (utils.validName(role) && !grants.hasOwnProperty(role)) {
709717
grants[role] = {};
710718
}
@@ -740,7 +748,7 @@ const utils = {
740748
// If possession (in action value or as a separate property) is
741749
// omitted, it will default to "any". e.g. "create" —>
742750
// "create:any"
743-
grantItem[res][ap] = utils.toStringArray(access.attributes);
751+
grantItem[res][ap] = utils.toValidRoleArray(access.attributes);
744752
});
745753
});
746754
},
@@ -764,7 +772,7 @@ const utils = {
764772
let resource: string;
765773
let attrsList: Array<string[]> = [];
766774
// get roles and extended roles in a flat array
767-
const roles: string[] = utils.getFlatRoles(grants, query.role);
775+
const roles: ValidRole[] = utils.getFlatRoles(grants, query.role);
768776
// iterate through roles and add permission attributes (array) of
769777
// each role to attrsList (array).
770778
roles.forEach((roleName: string, index: number) => {

‎test/ac.test.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { helper } from './helper';
1515
describe('Test Suite: AccessControl', () => {
1616

1717
// grant list fetched from DB (to be converted to a valid grants object)
18-
let grantList: any[] = [
18+
let grantList = [
1919
{ role: 'admin', resource: 'video', action: 'create:any', attributes: ['*'] },
2020
{ role: 'admin', resource: 'video', action: 'read:any', attributes: ['*'] },
2121
{ role: 'admin', resource: 'video', action: 'update:any', attributes: ['*'] },
@@ -24,11 +24,16 @@ describe('Test Suite: AccessControl', () => {
2424
{ role: 'user', resource: 'video', action: 'create:own', attributes: '*, !id' }, // comma-separated attrs
2525
{ role: 'user', resource: 'video', action: 'read:any', attributes: '*; !id' }, // semi-colon separated attrs
2626
{ role: 'user', resource: 'video', action: 'update:own', attributes: ['*', '!id'] }, // Array attrs
27-
{ role: 'user', resource: 'video', action: 'delete:own', attributes: ['*'] }
28-
];
27+
{ role: 'user', resource: 'video', action: 'delete:own', attributes: ['*'] },
28+
29+
{ role: 2, resource:7, action: 'create:any', attributes: ['*']}, // comma-separated attrs
30+
{ role: 2, resource:7, action: 'read:any', attributes: ['*']}, // semi-colon separated attrs
31+
{ role: 2, resource:7, action: 'update:any', attributes: ['*'] }, // Array attrs
32+
{ role: 2, resource:7, action: 'delete:any', attributes: ['*'] }
33+
] as const;
2934

3035
// valid grants object
31-
let grantsObject: any = {
36+
let grantsObject = {
3237
admin: {
3338
video: {
3439
'create:any': ['*'],
@@ -44,7 +49,15 @@ describe('Test Suite: AccessControl', () => {
4449
'update:own': ['*'],
4550
'delete:own': ['*']
4651
}
47-
}
52+
},
53+
2: {
54+
7: {
55+
'create:any': ['*'],
56+
'read:any': ['*'],
57+
'update:any': ['*'],
58+
'delete:any': ['*']
59+
}
60+
},
4861
};
4962

5063
// let ac;
@@ -203,8 +216,8 @@ describe('Test Suite: AccessControl', () => {
203216
expect(attrs3.length).toEqual(2);
204217

205218
// check roles & resources
206-
expect(ac.getRoles().length).toEqual(2);
207-
expect(ac.getResources().length).toEqual(1);
219+
expect(ac.getRoles().length).toEqual(3);
220+
expect(ac.getResources().length).toEqual(2);
208221
expect(ac.hasRole('admin')).toEqual(true);
209222
expect(ac.hasRole('user')).toEqual(true);
210223
expect(ac.hasRole(['user', 'admin'])).toEqual(true);
@@ -222,8 +235,10 @@ describe('Test Suite: AccessControl', () => {
222235
helper.expectACError(() => ac.removeRoles([]));
223236
helper.expectACError(() => ac.removeRoles(['']));
224237
helper.expectACError(() => ac.removeRoles(['none']));
238+
// no role named 69
239+
helper.expectACError(() => ac.removeRoles(['user', 69]));
225240
// no role named moderator
226-
helper.expectACError(() => ac.removeRoles(['user', 'moderator']));
241+
helper.expectACError(() => ac.removeRoles([2, 'moderator']));
227242
expect(ac.getRoles().length).toEqual(0);
228243
// removeRoles should accept a string or array
229244
ac.removeResources(['video']);

‎test/utils.test.ts

+23-22
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,30 @@ describe('Test Suite: utils (generic)', () => {
3636
expect(() => utils.hasDefined(null, 'prop')).toThrow();
3737
});
3838

39-
test('#toStringArray()', () => {
40-
expect(utils.toStringArray([])).toEqual([]);
41-
expect(utils.toStringArray('a')).toEqual(['a']);
42-
expect(utils.toStringArray('a,b,c')).toEqual(['a', 'b', 'c']);
43-
expect(utils.toStringArray('a, b, c \n')).toEqual(['a', 'b', 'c']);
44-
expect(utils.toStringArray('a ; b,c')).toEqual(['a', 'b', 'c']);
45-
expect(utils.toStringArray('a;b; c')).toEqual(['a', 'b', 'c']);
46-
expect(utils.toStringArray(1)).toEqual([]);
47-
expect(utils.toStringArray(true)).toEqual([]);
48-
expect(utils.toStringArray(false)).toEqual([]);
49-
expect(utils.toStringArray(null)).toEqual([]);
50-
expect(utils.toStringArray(undefined)).toEqual([]);
39+
test('#toValidRoleArray()', () => {
40+
expect(utils.toValidRoleArray([])).toEqual([]);
41+
expect(utils.toValidRoleArray('a')).toEqual(['a']);
42+
expect(utils.toValidRoleArray('a,b,c')).toEqual(['a', 'b', 'c']);
43+
expect(utils.toValidRoleArray('a, b, c \n')).toEqual(['a', 'b', 'c']);
44+
expect(utils.toValidRoleArray('a ; b,c')).toEqual(['a', 'b', 'c']);
45+
expect(utils.toValidRoleArray('a;b; c')).toEqual(['a', 'b', 'c']);
46+
expect(utils.toValidRoleArray(1)).toEqual([1]);
47+
expect(utils.toValidRoleArray([1, 2, 3])).toEqual([1, 2, 3]);
48+
expect(utils.toValidRoleArray(true)).toEqual([]);
49+
expect(utils.toValidRoleArray(false)).toEqual([]);
50+
expect(utils.toValidRoleArray(null)).toEqual([]);
51+
expect(utils.toValidRoleArray(undefined)).toEqual([]);
5152
});
5253

53-
test('#isFilledStringArray(), #isEmptyArray()', () => {
54-
expect(utils.isFilledStringArray([])).toBe(true); // allowed
55-
expect(utils.isFilledStringArray([''])).toBe(false);
56-
expect(utils.isFilledStringArray(['a'])).toBe(true);
57-
expect(utils.isFilledStringArray(['a', ''])).toBe(false);
58-
expect(utils.isFilledStringArray([1])).toBe(false);
59-
expect(utils.isFilledStringArray([null])).toBe(false);
60-
expect(utils.isFilledStringArray([undefined])).toBe(false);
61-
expect(utils.isFilledStringArray([false])).toBe(false);
54+
test('#isFilledValidRoleArray(), #isEmptyArray()', () => {
55+
expect(utils.isFilledValidRoleArray([])).toBe(true); // allowed
56+
expect(utils.isFilledValidRoleArray([''])).toBe(false);
57+
expect(utils.isFilledValidRoleArray(['a'])).toBe(true);
58+
expect(utils.isFilledValidRoleArray(['a', ''])).toBe(false);
59+
expect(utils.isFilledValidRoleArray([1])).toBe(true);
60+
expect(utils.isFilledValidRoleArray([null])).toBe(false);
61+
expect(utils.isFilledValidRoleArray([undefined])).toBe(false);
62+
expect(utils.isFilledValidRoleArray([false])).toBe(false);
6263

6364
expect(utils.isEmptyArray([])).toBe(true);
6465
expect(utils.isEmptyArray([1])).toBe(false);
@@ -157,7 +158,7 @@ describe('Test Suite: utils (core)', () => {
157158
helper.expectACError(() => utils.validName(invalid, true));
158159
expect(utils.validName(invalid, false)).toBe(false);
159160
expect(utils.validName('', false)).toBe(false);
160-
expect((utils as any).validName(1, false)).toBe(false);
161+
expect(utils.validName(1, false)).toBe(true);
161162
expect((utils as any).validName(null, false)).toBe(false);
162163
expect((utils as any).validName(true, false)).toBe(false);
163164

0 commit comments

Comments
 (0)
Please sign in to comment.