@aooth/arbac-core 0.1.76 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +44 -9
- package/dist/index.d.cts +8 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +44 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9,6 +9,8 @@ function arbacPatternToRegex(input) {
|
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/arbac.ts
|
|
12
|
+
/** Most memoized action matches per compiled (role, resource); past it, matched per call. */
|
|
13
|
+
const MAX_MEMOIZED_ACTIONS = 512;
|
|
12
14
|
/**
|
|
13
15
|
* Implements Advanced Role-Based Access Control (ARBAC) system.
|
|
14
16
|
* This class allows registering roles and resources, and evaluating access permissions based on user attributes and roles.
|
|
@@ -20,6 +22,7 @@ var Arbac = class {
|
|
|
20
22
|
roles = {};
|
|
21
23
|
resources = {};
|
|
22
24
|
warnedRoles = /* @__PURE__ */ new Set();
|
|
25
|
+
actionMatches = /* @__PURE__ */ new WeakMap();
|
|
23
26
|
/**
|
|
24
27
|
* Registers a new role with the ARBAC system.
|
|
25
28
|
*
|
|
@@ -119,28 +122,60 @@ var Arbac = class {
|
|
|
119
122
|
* runs it once (no attenuation) or twice (ceiling + narrowed credential view).
|
|
120
123
|
*/
|
|
121
124
|
async evaluateForRoles(res, roleIds, getAttrs, id) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
const perResource = this.resources[res.resource];
|
|
126
|
+
const roles = [];
|
|
127
|
+
for (const r of roleIds) {
|
|
128
|
+
const role = perResource[r];
|
|
129
|
+
if (role) roles.push(role);
|
|
130
|
+
else if (!this.roles[r] && !this.warnedRoles.has(r)) {
|
|
125
131
|
this.warnedRoles.add(r);
|
|
126
132
|
console.warn(`Role "${r}" assigned to user "${id}" does not exist.`);
|
|
127
133
|
}
|
|
128
|
-
|
|
129
|
-
}).filter(Boolean);
|
|
134
|
+
}
|
|
130
135
|
if (roles.length === 0) return { allowed: false };
|
|
131
|
-
|
|
136
|
+
const matches = roles.map((role) => this.matchAction(role, res.action));
|
|
137
|
+
for (const match of matches) if (match.deny) return { allowed: false };
|
|
132
138
|
const scopes = [];
|
|
133
139
|
let allowed = false;
|
|
134
|
-
|
|
140
|
+
let attrs;
|
|
141
|
+
let haveAttrs = false;
|
|
142
|
+
for (const match of matches) for (const rule of match.allow) {
|
|
135
143
|
allowed = true;
|
|
136
|
-
if (rule.scope)
|
|
137
|
-
|
|
144
|
+
if (rule.scope) {
|
|
145
|
+
if (!haveAttrs) {
|
|
146
|
+
attrs = await getAttrs();
|
|
147
|
+
haveAttrs = true;
|
|
148
|
+
}
|
|
149
|
+
scopes.push(rule.scope(attrs, String(id)));
|
|
150
|
+
} else scopes.push({});
|
|
138
151
|
}
|
|
139
152
|
return allowed ? {
|
|
140
153
|
allowed,
|
|
141
154
|
scopes
|
|
142
155
|
} : { allowed };
|
|
143
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The rules of one compiled (role, resource) that match `action`: whether
|
|
159
|
+
* any deny rule does, and the matching allow rules in declaration order.
|
|
160
|
+
* Memoized per compiled object (a re-registered role or resource compiles
|
|
161
|
+
* a fresh one, so the memo resets with it), bounded per object.
|
|
162
|
+
*/
|
|
163
|
+
matchAction(role, action) {
|
|
164
|
+
let perAction = this.actionMatches.get(role);
|
|
165
|
+
if (!perAction) {
|
|
166
|
+
perAction = /* @__PURE__ */ new Map();
|
|
167
|
+
this.actionMatches.set(role, perAction);
|
|
168
|
+
}
|
|
169
|
+
let match = perAction.get(action);
|
|
170
|
+
if (!match) {
|
|
171
|
+
match = {
|
|
172
|
+
deny: role.deny.some((rule) => rule._actionRegex.test(action)),
|
|
173
|
+
allow: role.allow.filter((rule) => rule._actionRegex.test(action))
|
|
174
|
+
};
|
|
175
|
+
if (perAction.size < MAX_MEMOIZED_ACTIONS) perAction.set(action, match);
|
|
176
|
+
}
|
|
177
|
+
return match;
|
|
178
|
+
}
|
|
144
179
|
};
|
|
145
180
|
//#endregion
|
|
146
181
|
exports.Arbac = Arbac;
|
package/dist/index.d.cts
CHANGED
|
@@ -51,6 +51,7 @@ declare class Arbac<TUserAttrs extends object, TScope extends object> {
|
|
|
51
51
|
protected roles: Record<string, TArbacRole<TUserAttrs, TScope> | undefined>;
|
|
52
52
|
protected resources: Record<string, Record<string, TArbacRoleForResource<TUserAttrs, TScope> | undefined> | undefined>;
|
|
53
53
|
private warnedRoles;
|
|
54
|
+
private actionMatches;
|
|
54
55
|
/**
|
|
55
56
|
* Registers a new role with the ARBAC system.
|
|
56
57
|
*
|
|
@@ -123,6 +124,13 @@ declare class Arbac<TUserAttrs extends object, TScope extends object> {
|
|
|
123
124
|
resource: string;
|
|
124
125
|
action: string;
|
|
125
126
|
}, roleIds: string[], getAttrs: () => Promise<TUserAttrs>, id: string | undefined): Promise<TArbacEvalResult<TScope>>;
|
|
127
|
+
/**
|
|
128
|
+
* The rules of one compiled (role, resource) that match `action`: whether
|
|
129
|
+
* any deny rule does, and the matching allow rules in declaration order.
|
|
130
|
+
* Memoized per compiled object (a re-registered role or resource compiles
|
|
131
|
+
* a fresh one, so the memo resets with it), bounded per object.
|
|
132
|
+
*/
|
|
133
|
+
private matchAction;
|
|
126
134
|
}
|
|
127
135
|
//#endregion
|
|
128
136
|
//#region src/utils.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -51,6 +51,7 @@ declare class Arbac<TUserAttrs extends object, TScope extends object> {
|
|
|
51
51
|
protected roles: Record<string, TArbacRole<TUserAttrs, TScope> | undefined>;
|
|
52
52
|
protected resources: Record<string, Record<string, TArbacRoleForResource<TUserAttrs, TScope> | undefined> | undefined>;
|
|
53
53
|
private warnedRoles;
|
|
54
|
+
private actionMatches;
|
|
54
55
|
/**
|
|
55
56
|
* Registers a new role with the ARBAC system.
|
|
56
57
|
*
|
|
@@ -123,6 +124,13 @@ declare class Arbac<TUserAttrs extends object, TScope extends object> {
|
|
|
123
124
|
resource: string;
|
|
124
125
|
action: string;
|
|
125
126
|
}, roleIds: string[], getAttrs: () => Promise<TUserAttrs>, id: string | undefined): Promise<TArbacEvalResult<TScope>>;
|
|
127
|
+
/**
|
|
128
|
+
* The rules of one compiled (role, resource) that match `action`: whether
|
|
129
|
+
* any deny rule does, and the matching allow rules in declaration order.
|
|
130
|
+
* Memoized per compiled object (a re-registered role or resource compiles
|
|
131
|
+
* a fresh one, so the memo resets with it), bounded per object.
|
|
132
|
+
*/
|
|
133
|
+
private matchAction;
|
|
126
134
|
}
|
|
127
135
|
//#endregion
|
|
128
136
|
//#region src/utils.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,8 @@ function arbacPatternToRegex(input) {
|
|
|
8
8
|
}
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/arbac.ts
|
|
11
|
+
/** Most memoized action matches per compiled (role, resource); past it, matched per call. */
|
|
12
|
+
const MAX_MEMOIZED_ACTIONS = 512;
|
|
11
13
|
/**
|
|
12
14
|
* Implements Advanced Role-Based Access Control (ARBAC) system.
|
|
13
15
|
* This class allows registering roles and resources, and evaluating access permissions based on user attributes and roles.
|
|
@@ -19,6 +21,7 @@ var Arbac = class {
|
|
|
19
21
|
roles = {};
|
|
20
22
|
resources = {};
|
|
21
23
|
warnedRoles = /* @__PURE__ */ new Set();
|
|
24
|
+
actionMatches = /* @__PURE__ */ new WeakMap();
|
|
22
25
|
/**
|
|
23
26
|
* Registers a new role with the ARBAC system.
|
|
24
27
|
*
|
|
@@ -118,28 +121,60 @@ var Arbac = class {
|
|
|
118
121
|
* runs it once (no attenuation) or twice (ceiling + narrowed credential view).
|
|
119
122
|
*/
|
|
120
123
|
async evaluateForRoles(res, roleIds, getAttrs, id) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const perResource = this.resources[res.resource];
|
|
125
|
+
const roles = [];
|
|
126
|
+
for (const r of roleIds) {
|
|
127
|
+
const role = perResource[r];
|
|
128
|
+
if (role) roles.push(role);
|
|
129
|
+
else if (!this.roles[r] && !this.warnedRoles.has(r)) {
|
|
124
130
|
this.warnedRoles.add(r);
|
|
125
131
|
console.warn(`Role "${r}" assigned to user "${id}" does not exist.`);
|
|
126
132
|
}
|
|
127
|
-
|
|
128
|
-
}).filter(Boolean);
|
|
133
|
+
}
|
|
129
134
|
if (roles.length === 0) return { allowed: false };
|
|
130
|
-
|
|
135
|
+
const matches = roles.map((role) => this.matchAction(role, res.action));
|
|
136
|
+
for (const match of matches) if (match.deny) return { allowed: false };
|
|
131
137
|
const scopes = [];
|
|
132
138
|
let allowed = false;
|
|
133
|
-
|
|
139
|
+
let attrs;
|
|
140
|
+
let haveAttrs = false;
|
|
141
|
+
for (const match of matches) for (const rule of match.allow) {
|
|
134
142
|
allowed = true;
|
|
135
|
-
if (rule.scope)
|
|
136
|
-
|
|
143
|
+
if (rule.scope) {
|
|
144
|
+
if (!haveAttrs) {
|
|
145
|
+
attrs = await getAttrs();
|
|
146
|
+
haveAttrs = true;
|
|
147
|
+
}
|
|
148
|
+
scopes.push(rule.scope(attrs, String(id)));
|
|
149
|
+
} else scopes.push({});
|
|
137
150
|
}
|
|
138
151
|
return allowed ? {
|
|
139
152
|
allowed,
|
|
140
153
|
scopes
|
|
141
154
|
} : { allowed };
|
|
142
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The rules of one compiled (role, resource) that match `action`: whether
|
|
158
|
+
* any deny rule does, and the matching allow rules in declaration order.
|
|
159
|
+
* Memoized per compiled object (a re-registered role or resource compiles
|
|
160
|
+
* a fresh one, so the memo resets with it), bounded per object.
|
|
161
|
+
*/
|
|
162
|
+
matchAction(role, action) {
|
|
163
|
+
let perAction = this.actionMatches.get(role);
|
|
164
|
+
if (!perAction) {
|
|
165
|
+
perAction = /* @__PURE__ */ new Map();
|
|
166
|
+
this.actionMatches.set(role, perAction);
|
|
167
|
+
}
|
|
168
|
+
let match = perAction.get(action);
|
|
169
|
+
if (!match) {
|
|
170
|
+
match = {
|
|
171
|
+
deny: role.deny.some((rule) => rule._actionRegex.test(action)),
|
|
172
|
+
allow: role.allow.filter((rule) => rule._actionRegex.test(action))
|
|
173
|
+
};
|
|
174
|
+
if (perAction.size < MAX_MEMOIZED_ACTIONS) perAction.set(action, match);
|
|
175
|
+
}
|
|
176
|
+
return match;
|
|
177
|
+
}
|
|
143
178
|
};
|
|
144
179
|
//#endregion
|
|
145
180
|
export { Arbac, arbacPatternToRegex };
|