@ahmed109/gatekeeper-core 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +59 -2
- package/dist/index.cjs +378 -0
- package/dist/index.d.cts +242 -0
- package/dist/index.d.ts +242 -0
- package/dist/index.js +338 -0
- package/package.json +51 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmed Tamer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ahmed109/gatekeeper-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Pure, framework-independent authorization and feature-flag evaluator for TypeScript.
|
|
4
|
+
|
|
5
|
+
Part of the **Gatekeeper** toolkit. Zero runtime dependencies, deterministic evaluation, and full TypeScript type safety for role-based (RBAC) and attribute-based (ABAC) access control.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
`ash
|
|
10
|
+
npm install @ahmed109/gatekeeper-core
|
|
11
|
+
|
|
12
|
+
# or
|
|
13
|
+
|
|
14
|
+
pnpm add @ahmed109/gatekeeper-core
|
|
15
|
+
|
|
16
|
+
# or
|
|
17
|
+
|
|
18
|
+
yarn add @ahmed109/gatekeeper-core
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Zero runtime dependencies**: Pure functional logic running identically in Node.js, browsers, Edge runtimes, and Cloudflare Workers.
|
|
24
|
+
- **Layered Privilege Model**: Evaluates base role permissions (dmin, manager, member, or custom) combined with explicit user-level direct grants.
|
|
25
|
+
- **Feature Flag Engine**: Side-by-side authorization and feature-flag evaluation.
|
|
26
|
+
- **Compound Checks**: Built-in support for can(), canAll(), and canAny().
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
` ypescript
|
|
31
|
+
import { can, canAll, isFeatureEnabled, type GatekeeperUser } from '@ahmed109/gatekeeper-core';
|
|
32
|
+
|
|
33
|
+
const user: GatekeeperUser = {
|
|
34
|
+
id: 'usr_123',
|
|
35
|
+
name: 'Alice Admin',
|
|
36
|
+
email: 'alice@example.com',
|
|
37
|
+
role: 'admin',
|
|
38
|
+
permissions: ['custom.override'],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Check single permission
|
|
42
|
+
if (can(user, 'users.delete')) {
|
|
43
|
+
console.log('User can delete users');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check multiple permissions
|
|
47
|
+
if (canAll(user, ['users.read', 'settings.manage'])) {
|
|
48
|
+
console.log('User possesses all required permissions');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Evaluate feature flags
|
|
52
|
+
const flags = { 'new-dashboard': true, 'beta-analytics': false };
|
|
53
|
+
if (isFeatureEnabled('new-dashboard', flags)) {
|
|
54
|
+
console.log('Feature is enabled');
|
|
55
|
+
}
|
|
56
|
+
`
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT © [Ahmed Tamer](https://github.com/Ahmed1092002)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_ROLE_PERMISSIONS: () => DEFAULT_ROLE_PERMISSIONS,
|
|
24
|
+
can: () => can,
|
|
25
|
+
canAll: () => canAll,
|
|
26
|
+
canAny: () => canAny,
|
|
27
|
+
createSubject: () => createSubject,
|
|
28
|
+
defineRolePermissions: () => defineRolePermissions,
|
|
29
|
+
evaluatePermission: () => evaluatePermission,
|
|
30
|
+
fromJwtClaims: () => fromJwtClaims,
|
|
31
|
+
fromPermissions: () => fromPermissions,
|
|
32
|
+
fromRoles: () => fromRoles,
|
|
33
|
+
getUserEffectivePermissions: () => getUserEffectivePermissions,
|
|
34
|
+
isFeatureEnabled: () => isFeatureEnabled,
|
|
35
|
+
normalizeFeatureFlags: () => normalizeFeatureFlags,
|
|
36
|
+
parseEvaluateOptions: () => parseEvaluateOptions
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/roles.ts
|
|
41
|
+
var DEFAULT_ROLE_PERMISSIONS = {
|
|
42
|
+
admin: ["users.read", "users.create", "users.delete", "reports.view", "settings.manage"],
|
|
43
|
+
manager: ["users.read", "reports.view"],
|
|
44
|
+
member: ["reports.view"]
|
|
45
|
+
};
|
|
46
|
+
function defineRolePermissions(customMap, baseMap = DEFAULT_ROLE_PERMISSIONS) {
|
|
47
|
+
if (!customMap) {
|
|
48
|
+
return { ...baseMap };
|
|
49
|
+
}
|
|
50
|
+
const merged = { ...baseMap };
|
|
51
|
+
for (const [role, perms] of Object.entries(customMap)) {
|
|
52
|
+
if (perms) {
|
|
53
|
+
merged[role] = [...perms];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return merged;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/evaluator.ts
|
|
60
|
+
function parseEvaluateOptions(options) {
|
|
61
|
+
if (!options || typeof options !== "object") {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
if ("roleMap" in options || "context" in options || "policy" in options) {
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
const values = Object.values(options);
|
|
68
|
+
if (values.length === 0 || values.every((v) => Array.isArray(v))) {
|
|
69
|
+
return { roleMap: options };
|
|
70
|
+
}
|
|
71
|
+
return options;
|
|
72
|
+
}
|
|
73
|
+
function getUserEffectivePermissions(subject, roleMapOrOptions) {
|
|
74
|
+
const permissions = /* @__PURE__ */ new Set();
|
|
75
|
+
if (!subject) {
|
|
76
|
+
return permissions;
|
|
77
|
+
}
|
|
78
|
+
const opts = parseEvaluateOptions(roleMapOrOptions);
|
|
79
|
+
const roleMap = opts.roleMap ?? DEFAULT_ROLE_PERMISSIONS;
|
|
80
|
+
const rolesToEvaluate = /* @__PURE__ */ new Set();
|
|
81
|
+
if (Array.isArray(subject.roles)) {
|
|
82
|
+
for (const r of subject.roles) {
|
|
83
|
+
if (r) rolesToEvaluate.add(r);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (subject.role) {
|
|
87
|
+
rolesToEvaluate.add(subject.role);
|
|
88
|
+
}
|
|
89
|
+
for (const role of rolesToEvaluate) {
|
|
90
|
+
const rolePerms = roleMap[role];
|
|
91
|
+
if (rolePerms && Array.isArray(rolePerms)) {
|
|
92
|
+
for (const perm of rolePerms) {
|
|
93
|
+
permissions.add(perm);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(subject.permissions)) {
|
|
98
|
+
for (const perm of subject.permissions) {
|
|
99
|
+
if (perm) permissions.add(perm);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return permissions;
|
|
103
|
+
}
|
|
104
|
+
function can(subject, permission, options) {
|
|
105
|
+
if (!subject || !permission) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const opts = parseEvaluateOptions(options);
|
|
109
|
+
if (opts.policy) {
|
|
110
|
+
const decision = opts.policy(subject, permission, opts.context);
|
|
111
|
+
if (typeof decision === "boolean") {
|
|
112
|
+
return decision;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const effective = getUserEffectivePermissions(subject, options !== void 0 ? opts : void 0);
|
|
116
|
+
return effective.has(permission);
|
|
117
|
+
}
|
|
118
|
+
function canAll(subject, permissions, options) {
|
|
119
|
+
if (!subject || !permissions.length) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return permissions.every((p) => can(subject, p, options));
|
|
123
|
+
}
|
|
124
|
+
function canAny(subject, permissions, options) {
|
|
125
|
+
if (!subject || !permissions.length) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return permissions.some((p) => can(subject, p, options));
|
|
129
|
+
}
|
|
130
|
+
function evaluatePermission(subject, permission, options) {
|
|
131
|
+
if (!subject) {
|
|
132
|
+
return {
|
|
133
|
+
allowed: false,
|
|
134
|
+
reason: "No authenticated user provided.",
|
|
135
|
+
missingPermissions: [permission]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const opts = parseEvaluateOptions(options);
|
|
139
|
+
if (opts.policy) {
|
|
140
|
+
const decision = opts.policy(subject, permission, opts.context);
|
|
141
|
+
if (typeof decision === "boolean") {
|
|
142
|
+
return {
|
|
143
|
+
allowed: decision,
|
|
144
|
+
reason: decision ? "Permission granted by policy." : "Permission denied by policy.",
|
|
145
|
+
missingPermissions: decision ? void 0 : [permission]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const effective = getUserEffectivePermissions(subject, options !== void 0 ? opts : void 0);
|
|
150
|
+
const allowed = effective.has(permission);
|
|
151
|
+
if (allowed) {
|
|
152
|
+
return {
|
|
153
|
+
allowed: true,
|
|
154
|
+
reason: "Permission granted."
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const roleDesc = subject.role ?? (subject.roles?.length ? subject.roles.join(", ") : "none");
|
|
158
|
+
return {
|
|
159
|
+
allowed: false,
|
|
160
|
+
reason: `User with role '${roleDesc}' lacks required permission '${permission}'.`,
|
|
161
|
+
missingPermissions: [permission]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function normalizeFeatureFlags(flags) {
|
|
165
|
+
if (!flags) {
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(flags)) {
|
|
169
|
+
const result = {};
|
|
170
|
+
for (const flag of flags) {
|
|
171
|
+
result[flag.key] = flag.enabled;
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
return { ...flags };
|
|
176
|
+
}
|
|
177
|
+
function isFeatureEnabled(flags, flagKey) {
|
|
178
|
+
if (!flags || !flagKey) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(flags)) {
|
|
182
|
+
const found = flags.find((f) => f.key === flagKey);
|
|
183
|
+
return Boolean(found?.enabled);
|
|
184
|
+
}
|
|
185
|
+
return Boolean(flags[flagKey]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/adapters.ts
|
|
189
|
+
function fromRoles(idOrInput, roles, attributes) {
|
|
190
|
+
if (typeof idOrInput === "object" && idOrInput !== null) {
|
|
191
|
+
const rolesList2 = [];
|
|
192
|
+
if (Array.isArray(idOrInput.roles)) {
|
|
193
|
+
rolesList2.push(...idOrInput.roles);
|
|
194
|
+
} else if (idOrInput.roles) {
|
|
195
|
+
rolesList2.push(idOrInput.roles);
|
|
196
|
+
}
|
|
197
|
+
if (idOrInput.role && !rolesList2.includes(idOrInput.role)) {
|
|
198
|
+
rolesList2.push(idOrInput.role);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
id: String(idOrInput.id),
|
|
202
|
+
roles: rolesList2,
|
|
203
|
+
role: idOrInput.role ?? rolesList2[0],
|
|
204
|
+
...idOrInput.permissions ? { permissions: idOrInput.permissions } : {},
|
|
205
|
+
...idOrInput.attributes ? { attributes: idOrInput.attributes } : {},
|
|
206
|
+
...idOrInput.name ? { name: idOrInput.name } : {},
|
|
207
|
+
...idOrInput.email ? { email: idOrInput.email } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const rolesList = [];
|
|
211
|
+
if (Array.isArray(roles)) {
|
|
212
|
+
rolesList.push(...roles);
|
|
213
|
+
} else if (roles) {
|
|
214
|
+
rolesList.push(roles);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
id: String(idOrInput),
|
|
218
|
+
roles: rolesList,
|
|
219
|
+
role: rolesList[0],
|
|
220
|
+
...attributes ? { attributes } : {}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function fromPermissions(idOrInput, permissions, attributes) {
|
|
224
|
+
if (typeof idOrInput === "object" && idOrInput !== null) {
|
|
225
|
+
return {
|
|
226
|
+
id: String(idOrInput.id),
|
|
227
|
+
roles: [],
|
|
228
|
+
permissions: Array.isArray(idOrInput.permissions) ? [...idOrInput.permissions] : [],
|
|
229
|
+
...idOrInput.attributes ? { attributes: idOrInput.attributes } : {},
|
|
230
|
+
...idOrInput.name ? { name: idOrInput.name } : {},
|
|
231
|
+
...idOrInput.email ? { email: idOrInput.email } : {}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
id: String(idOrInput),
|
|
236
|
+
roles: [],
|
|
237
|
+
permissions: Array.isArray(permissions) ? [...permissions] : [],
|
|
238
|
+
...attributes ? { attributes } : {}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function getByPath(obj, path) {
|
|
242
|
+
const parts = path.split(".");
|
|
243
|
+
let current = obj;
|
|
244
|
+
for (const part of parts) {
|
|
245
|
+
if (current && typeof current === "object" && part in current) {
|
|
246
|
+
current = current[part];
|
|
247
|
+
} else {
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return current;
|
|
252
|
+
}
|
|
253
|
+
function fromJwtClaims(claims, options = {}) {
|
|
254
|
+
if (!claims || typeof claims !== "object") {
|
|
255
|
+
return { id: "", roles: [], permissions: [] };
|
|
256
|
+
}
|
|
257
|
+
const idFields = options.idField ? Array.isArray(options.idField) ? options.idField : [options.idField] : ["sub", "id"];
|
|
258
|
+
let resolvedId = "";
|
|
259
|
+
for (const field of idFields) {
|
|
260
|
+
const val = getByPath(claims, field);
|
|
261
|
+
if (val !== void 0 && val !== null && String(val).trim()) {
|
|
262
|
+
resolvedId = String(val);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const rolesFields = options.rolesField ? Array.isArray(options.rolesField) ? options.rolesField : [options.rolesField] : ["roles", "role", "realm_access.roles"];
|
|
267
|
+
const resolvedRoles = [];
|
|
268
|
+
for (const field of rolesFields) {
|
|
269
|
+
const val = getByPath(claims, field);
|
|
270
|
+
if (Array.isArray(val)) {
|
|
271
|
+
for (const item of val) {
|
|
272
|
+
if (typeof item === "string") {
|
|
273
|
+
resolvedRoles.push(item);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (resolvedRoles.length > 0) break;
|
|
277
|
+
} else if (typeof val === "string" && val.trim()) {
|
|
278
|
+
resolvedRoles.push(val);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const permFields = options.permissionsField ? Array.isArray(options.permissionsField) ? options.permissionsField : [options.permissionsField] : ["permissions", "perms"];
|
|
283
|
+
const resolvedPerms = [];
|
|
284
|
+
for (const field of permFields) {
|
|
285
|
+
const val = getByPath(claims, field);
|
|
286
|
+
if (Array.isArray(val)) {
|
|
287
|
+
for (const item of val) {
|
|
288
|
+
if (typeof item === "string") {
|
|
289
|
+
resolvedPerms.push(item);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (resolvedPerms.length > 0) break;
|
|
293
|
+
} else if (typeof val === "string" && val.trim()) {
|
|
294
|
+
resolvedPerms.push(val);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const attributes = {};
|
|
299
|
+
if (options.attributesFields && Array.isArray(options.attributesFields)) {
|
|
300
|
+
for (const attrField of options.attributesFields) {
|
|
301
|
+
const val = getByPath(claims, attrField);
|
|
302
|
+
if (val !== void 0) {
|
|
303
|
+
attributes[attrField] = val;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
const excludedKeys = /* @__PURE__ */ new Set([
|
|
308
|
+
"sub",
|
|
309
|
+
"id",
|
|
310
|
+
"roles",
|
|
311
|
+
"role",
|
|
312
|
+
"realm_access",
|
|
313
|
+
"permissions",
|
|
314
|
+
"perms"
|
|
315
|
+
]);
|
|
316
|
+
for (const [k, v] of Object.entries(claims)) {
|
|
317
|
+
if (!excludedKeys.has(k) && v !== void 0) {
|
|
318
|
+
attributes[k] = v;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const email = typeof claims.email === "string" ? claims.email : void 0;
|
|
323
|
+
const name = typeof claims.name === "string" ? claims.name : typeof claims.preferred_username === "string" ? claims.preferred_username : void 0;
|
|
324
|
+
return {
|
|
325
|
+
id: resolvedId,
|
|
326
|
+
roles: resolvedRoles,
|
|
327
|
+
role: resolvedRoles[0],
|
|
328
|
+
permissions: resolvedPerms,
|
|
329
|
+
attributes,
|
|
330
|
+
...name ? { name } : {},
|
|
331
|
+
...email ? { email } : {}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function createSubject(source, mapping) {
|
|
335
|
+
if (typeof mapping === "function") {
|
|
336
|
+
return mapping(source);
|
|
337
|
+
}
|
|
338
|
+
const rawRoles = mapping.getRoles ? mapping.getRoles(source) : void 0;
|
|
339
|
+
const rolesList = [];
|
|
340
|
+
if (Array.isArray(rawRoles)) {
|
|
341
|
+
for (const r of rawRoles) {
|
|
342
|
+
if (r) rolesList.push(r);
|
|
343
|
+
}
|
|
344
|
+
} else if (rawRoles) {
|
|
345
|
+
rolesList.push(rawRoles);
|
|
346
|
+
}
|
|
347
|
+
const rawPerms = mapping.getPermissions ? mapping.getPermissions(source) : void 0;
|
|
348
|
+
const permsList = Array.isArray(rawPerms) ? [...rawPerms] : [];
|
|
349
|
+
const attributes = mapping.getAttributes ? mapping.getAttributes(source) : void 0;
|
|
350
|
+
const name = mapping.getName ? mapping.getName(source) : void 0;
|
|
351
|
+
const email = mapping.getEmail ? mapping.getEmail(source) : void 0;
|
|
352
|
+
return {
|
|
353
|
+
id: mapping.getId(source),
|
|
354
|
+
roles: rolesList,
|
|
355
|
+
role: rolesList[0],
|
|
356
|
+
permissions: permsList,
|
|
357
|
+
...attributes ? { attributes } : {},
|
|
358
|
+
...name ? { name } : {},
|
|
359
|
+
...email ? { email } : {}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
363
|
+
0 && (module.exports = {
|
|
364
|
+
DEFAULT_ROLE_PERMISSIONS,
|
|
365
|
+
can,
|
|
366
|
+
canAll,
|
|
367
|
+
canAny,
|
|
368
|
+
createSubject,
|
|
369
|
+
defineRolePermissions,
|
|
370
|
+
evaluatePermission,
|
|
371
|
+
fromJwtClaims,
|
|
372
|
+
fromPermissions,
|
|
373
|
+
fromRoles,
|
|
374
|
+
getUserEffectivePermissions,
|
|
375
|
+
isFeatureEnabled,
|
|
376
|
+
normalizeFeatureFlags,
|
|
377
|
+
parseEvaluateOptions
|
|
378
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard permissions supported out-of-the-box.
|
|
3
|
+
* Users can also supply arbitrary string permissions.
|
|
4
|
+
*/
|
|
5
|
+
type StandardPermission = 'users.read' | 'users.create' | 'users.delete' | 'reports.view' | 'settings.manage';
|
|
6
|
+
type Permission = StandardPermission | (string & {});
|
|
7
|
+
/**
|
|
8
|
+
* Built-in standard roles.
|
|
9
|
+
*/
|
|
10
|
+
type StandardRole = 'admin' | 'manager' | 'member';
|
|
11
|
+
type Role = StandardRole | (string & {});
|
|
12
|
+
/**
|
|
13
|
+
* Flexible authorization subject supporting single role, multiple roles,
|
|
14
|
+
* direct permissions, and contextual attributes.
|
|
15
|
+
*/
|
|
16
|
+
interface AuthorizationSubject {
|
|
17
|
+
id: string;
|
|
18
|
+
/** Legacy single-role support */
|
|
19
|
+
role?: Role;
|
|
20
|
+
/** Preferred multiple-role support */
|
|
21
|
+
roles?: Role[];
|
|
22
|
+
name?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Optional direct permissions assigned directly to this subject,
|
|
26
|
+
* extending or complementing role permissions.
|
|
27
|
+
*/
|
|
28
|
+
permissions?: Permission[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional metadata attributes such as tenantId, plan, teamId, etc.
|
|
31
|
+
*/
|
|
32
|
+
attributes?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Backward-compatible alias for AuthorizationSubject.
|
|
36
|
+
*/
|
|
37
|
+
type GatekeeperUser = AuthorizationSubject;
|
|
38
|
+
/**
|
|
39
|
+
* Optional policy callback for scoped or attribute-based authorization.
|
|
40
|
+
* Returning true/false decides immediately. Returning undefined falls back to RBAC.
|
|
41
|
+
*/
|
|
42
|
+
type AuthorizationPolicy = (subject: AuthorizationSubject, permission: Permission, context?: Record<string, unknown>) => boolean | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Options passed to authorization checks.
|
|
45
|
+
*/
|
|
46
|
+
interface EvaluateOptions {
|
|
47
|
+
roleMap?: RolePermissionsMap;
|
|
48
|
+
context?: Record<string, unknown>;
|
|
49
|
+
policy?: AuthorizationPolicy;
|
|
50
|
+
}
|
|
51
|
+
type RoleMapOrOptions = RolePermissionsMap | EvaluateOptions;
|
|
52
|
+
/**
|
|
53
|
+
* Represents a feature flag with its key and active status.
|
|
54
|
+
*/
|
|
55
|
+
interface FeatureFlag {
|
|
56
|
+
key: string;
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
description?: string;
|
|
59
|
+
metadata?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detailed outcome of an authorization decision.
|
|
63
|
+
*/
|
|
64
|
+
interface AuthorizationResult {
|
|
65
|
+
allowed: boolean;
|
|
66
|
+
reason?: string;
|
|
67
|
+
missingPermissions?: Permission[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Mapping from roles to their granted permissions.
|
|
71
|
+
*/
|
|
72
|
+
type RolePermissionsMap = Record<Role, readonly Permission[] | Permission[]>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default role-to-permissions map for Gatekeeper.
|
|
76
|
+
*
|
|
77
|
+
* - Admin: Full access across user management, reports, and settings.
|
|
78
|
+
* - Manager: Can read users and view reports.
|
|
79
|
+
* - Member: Can view reports.
|
|
80
|
+
*/
|
|
81
|
+
declare const DEFAULT_ROLE_PERMISSIONS: RolePermissionsMap;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a merged role-to-permissions map by extending or overriding the defaults.
|
|
84
|
+
*/
|
|
85
|
+
declare function defineRolePermissions(customMap?: Partial<RolePermissionsMap>, baseMap?: RolePermissionsMap): RolePermissionsMap;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes either a legacy RolePermissionsMap or a modern EvaluateOptions object.
|
|
89
|
+
* An empty plain object `{}` is treated as an explicit empty RolePermissionsMap (`{ roleMap: {} }`).
|
|
90
|
+
* When options is omitted/undefined, an empty options object is returned (roleMap remains undefined).
|
|
91
|
+
*/
|
|
92
|
+
declare function parseEvaluateOptions(options?: RoleMapOrOptions): EvaluateOptions;
|
|
93
|
+
/**
|
|
94
|
+
* Returns all effective permissions granted to a subject, combining
|
|
95
|
+
* permissions from every role in `roles`, the legacy `role` field,
|
|
96
|
+
* and direct permissions assigned in `permissions`.
|
|
97
|
+
* Deduplicates permissions and denies safely if no roles or permissions exist.
|
|
98
|
+
*/
|
|
99
|
+
declare function getUserEffectivePermissions(subject: AuthorizationSubject | null | undefined, roleMapOrOptions?: RoleMapOrOptions): Set<Permission>;
|
|
100
|
+
/**
|
|
101
|
+
* Evaluates whether a subject has a specific permission.
|
|
102
|
+
* Supports scoped policy checks and legacy roleMap options.
|
|
103
|
+
*
|
|
104
|
+
* @param subject - The subject to check.
|
|
105
|
+
* @param permission - The required permission string.
|
|
106
|
+
* @param options - Optional roleMap or EvaluateOptions ({ roleMap, context, policy }).
|
|
107
|
+
* @returns boolean indicating if access is granted.
|
|
108
|
+
*/
|
|
109
|
+
declare function can(subject: AuthorizationSubject | null | undefined, permission: Permission, options?: RoleMapOrOptions): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Checks if a subject has ALL of the requested permissions.
|
|
112
|
+
*/
|
|
113
|
+
declare function canAll(subject: AuthorizationSubject | null | undefined, permissions: Permission[], options?: RoleMapOrOptions): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Checks if a subject has AT LEAST ONE of the requested permissions.
|
|
116
|
+
*/
|
|
117
|
+
declare function canAny(subject: AuthorizationSubject | null | undefined, permissions: Permission[], options?: RoleMapOrOptions): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Evaluates access with detailed diagnostic reasons.
|
|
120
|
+
*/
|
|
121
|
+
declare function evaluatePermission(subject: AuthorizationSubject | null | undefined, permission: Permission, options?: RoleMapOrOptions): AuthorizationResult;
|
|
122
|
+
/**
|
|
123
|
+
* Normalizes feature flag inputs (either an array of FeatureFlag or a record of boolean flags)
|
|
124
|
+
* into a standard Record<string, boolean>.
|
|
125
|
+
*/
|
|
126
|
+
declare function normalizeFeatureFlags(flags?: Record<string, boolean> | FeatureFlag[]): Record<string, boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Checks whether a feature flag is enabled.
|
|
129
|
+
*/
|
|
130
|
+
declare function isFeatureEnabled(flags: Record<string, boolean> | FeatureFlag[] | undefined, flagKey: string): boolean;
|
|
131
|
+
|
|
132
|
+
interface FromRolesInput {
|
|
133
|
+
id: string;
|
|
134
|
+
role?: Role;
|
|
135
|
+
roles?: Role[] | Role;
|
|
136
|
+
permissions?: Permission[];
|
|
137
|
+
attributes?: Record<string, unknown>;
|
|
138
|
+
name?: string;
|
|
139
|
+
email?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes an identity with one or multiple roles into a standard AuthorizationSubject.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* // Single role
|
|
146
|
+
* fromRoles('u1', 'admin');
|
|
147
|
+
*
|
|
148
|
+
* // Multiple roles with metadata attributes
|
|
149
|
+
* fromRoles('u1', ['billing-manager', 'support-agent'], { tenantId: 't-123' });
|
|
150
|
+
*
|
|
151
|
+
* // Object input
|
|
152
|
+
* fromRoles({ id: 'u1', roles: ['editor', 'viewer'] });
|
|
153
|
+
*/
|
|
154
|
+
declare function fromRoles(idOrInput: string | FromRolesInput, roles?: Role[] | Role, attributes?: Record<string, unknown>): AuthorizationSubject;
|
|
155
|
+
interface FromPermissionsInput {
|
|
156
|
+
id: string;
|
|
157
|
+
permissions: Permission[];
|
|
158
|
+
attributes?: Record<string, unknown>;
|
|
159
|
+
name?: string;
|
|
160
|
+
email?: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Normalizes an identity with direct permissions into an AuthorizationSubject.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* fromPermissions('u1', ['users.read', 'reports.view']);
|
|
167
|
+
* fromPermissions({ id: 'u1', permissions: ['users.read'] });
|
|
168
|
+
*/
|
|
169
|
+
declare function fromPermissions(idOrInput: string | FromPermissionsInput, permissions?: Permission[], attributes?: Record<string, unknown>): AuthorizationSubject;
|
|
170
|
+
interface JwtClaimsOptions {
|
|
171
|
+
/** Field name(s) to check for subject ID. Defaults to ['sub', 'id'] */
|
|
172
|
+
idField?: string | string[];
|
|
173
|
+
/** Field name(s) or dot-paths to check for roles. Defaults to ['roles', 'role', 'realm_access.roles'] */
|
|
174
|
+
rolesField?: string | string[];
|
|
175
|
+
/** Field name(s) to check for permissions. Defaults to ['permissions', 'perms'] */
|
|
176
|
+
permissionsField?: string | string[];
|
|
177
|
+
/** Optional specific claim keys to copy into subject attributes */
|
|
178
|
+
attributesFields?: string[];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Normalizes JWT / OIDC claims payload into an AuthorizationSubject.
|
|
182
|
+
* Supports standard claims as well as Keycloak realm_access and custom claims.
|
|
183
|
+
*
|
|
184
|
+
* Note: This is a generic normalization helper and does not validate token signatures.
|
|
185
|
+
* Signature verification must be performed by your upstream authentication layer.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Keycloak JWT with realm_access.roles:
|
|
189
|
+
* const token = {
|
|
190
|
+
* sub: 'user_kc_123',
|
|
191
|
+
* realm_access: { roles: ['admin', 'billing'] },
|
|
192
|
+
* email: 'alex@example.com'
|
|
193
|
+
* };
|
|
194
|
+
* const subject = fromJwtClaims(token);
|
|
195
|
+
*/
|
|
196
|
+
declare function fromJwtClaims(claims: Record<string, unknown>, options?: JwtClaimsOptions): AuthorizationSubject;
|
|
197
|
+
interface SubjectMappingConfig<TSource> {
|
|
198
|
+
getId: (source: TSource) => string;
|
|
199
|
+
getRoles?: (source: TSource) => Role[] | Role | undefined | null;
|
|
200
|
+
getPermissions?: (source: TSource) => Permission[] | undefined | null;
|
|
201
|
+
getAttributes?: (source: TSource) => Record<string, unknown> | undefined | null;
|
|
202
|
+
getName?: (source: TSource) => string | undefined | null;
|
|
203
|
+
getEmail?: (source: TSource) => string | undefined | null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Creates an AuthorizationSubject by mapping any arbitrary domain or database model.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* // 1. Simple database record
|
|
210
|
+
* interface DbUser {
|
|
211
|
+
* user_id: string;
|
|
212
|
+
* primary_role: string;
|
|
213
|
+
* is_active: boolean;
|
|
214
|
+
* }
|
|
215
|
+
* const subject = createSubject(dbUser, {
|
|
216
|
+
* getId: (u) => u.user_id,
|
|
217
|
+
* getRoles: (u) => [u.primary_role],
|
|
218
|
+
* getAttributes: (u) => ({ isActive: u.is_active }),
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* // 2. Custom membership model with nested roles
|
|
222
|
+
* interface OrgMembership {
|
|
223
|
+
* memberId: string;
|
|
224
|
+
* organization: { orgId: string };
|
|
225
|
+
* access: { roles: string[]; customGrants?: string[] };
|
|
226
|
+
* }
|
|
227
|
+
* const memberSubject = createSubject(membership, {
|
|
228
|
+
* getId: (m) => m.memberId,
|
|
229
|
+
* getRoles: (m) => m.access.roles,
|
|
230
|
+
* getPermissions: (m) => m.access.customGrants,
|
|
231
|
+
* getAttributes: (m) => ({ orgId: m.organization.orgId }),
|
|
232
|
+
* });
|
|
233
|
+
*
|
|
234
|
+
* // 3. Functional transformer
|
|
235
|
+
* const customSubject = createSubject(rawInput, (input) => ({
|
|
236
|
+
* id: input.id,
|
|
237
|
+
* roles: input.assignedRoles,
|
|
238
|
+
* }));
|
|
239
|
+
*/
|
|
240
|
+
declare function createSubject<TSource>(source: TSource, mapping: SubjectMappingConfig<TSource> | ((source: TSource) => AuthorizationSubject)): AuthorizationSubject;
|
|
241
|
+
|
|
242
|
+
export { type AuthorizationPolicy, type AuthorizationResult, type AuthorizationSubject, DEFAULT_ROLE_PERMISSIONS, type EvaluateOptions, type FeatureFlag, type FromPermissionsInput, type FromRolesInput, type GatekeeperUser, type JwtClaimsOptions, type Permission, type Role, type RoleMapOrOptions, type RolePermissionsMap, type StandardPermission, type StandardRole, type SubjectMappingConfig, can, canAll, canAny, createSubject, defineRolePermissions, evaluatePermission, fromJwtClaims, fromPermissions, fromRoles, getUserEffectivePermissions, isFeatureEnabled, normalizeFeatureFlags, parseEvaluateOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard permissions supported out-of-the-box.
|
|
3
|
+
* Users can also supply arbitrary string permissions.
|
|
4
|
+
*/
|
|
5
|
+
type StandardPermission = 'users.read' | 'users.create' | 'users.delete' | 'reports.view' | 'settings.manage';
|
|
6
|
+
type Permission = StandardPermission | (string & {});
|
|
7
|
+
/**
|
|
8
|
+
* Built-in standard roles.
|
|
9
|
+
*/
|
|
10
|
+
type StandardRole = 'admin' | 'manager' | 'member';
|
|
11
|
+
type Role = StandardRole | (string & {});
|
|
12
|
+
/**
|
|
13
|
+
* Flexible authorization subject supporting single role, multiple roles,
|
|
14
|
+
* direct permissions, and contextual attributes.
|
|
15
|
+
*/
|
|
16
|
+
interface AuthorizationSubject {
|
|
17
|
+
id: string;
|
|
18
|
+
/** Legacy single-role support */
|
|
19
|
+
role?: Role;
|
|
20
|
+
/** Preferred multiple-role support */
|
|
21
|
+
roles?: Role[];
|
|
22
|
+
name?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Optional direct permissions assigned directly to this subject,
|
|
26
|
+
* extending or complementing role permissions.
|
|
27
|
+
*/
|
|
28
|
+
permissions?: Permission[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional metadata attributes such as tenantId, plan, teamId, etc.
|
|
31
|
+
*/
|
|
32
|
+
attributes?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Backward-compatible alias for AuthorizationSubject.
|
|
36
|
+
*/
|
|
37
|
+
type GatekeeperUser = AuthorizationSubject;
|
|
38
|
+
/**
|
|
39
|
+
* Optional policy callback for scoped or attribute-based authorization.
|
|
40
|
+
* Returning true/false decides immediately. Returning undefined falls back to RBAC.
|
|
41
|
+
*/
|
|
42
|
+
type AuthorizationPolicy = (subject: AuthorizationSubject, permission: Permission, context?: Record<string, unknown>) => boolean | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Options passed to authorization checks.
|
|
45
|
+
*/
|
|
46
|
+
interface EvaluateOptions {
|
|
47
|
+
roleMap?: RolePermissionsMap;
|
|
48
|
+
context?: Record<string, unknown>;
|
|
49
|
+
policy?: AuthorizationPolicy;
|
|
50
|
+
}
|
|
51
|
+
type RoleMapOrOptions = RolePermissionsMap | EvaluateOptions;
|
|
52
|
+
/**
|
|
53
|
+
* Represents a feature flag with its key and active status.
|
|
54
|
+
*/
|
|
55
|
+
interface FeatureFlag {
|
|
56
|
+
key: string;
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
description?: string;
|
|
59
|
+
metadata?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detailed outcome of an authorization decision.
|
|
63
|
+
*/
|
|
64
|
+
interface AuthorizationResult {
|
|
65
|
+
allowed: boolean;
|
|
66
|
+
reason?: string;
|
|
67
|
+
missingPermissions?: Permission[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Mapping from roles to their granted permissions.
|
|
71
|
+
*/
|
|
72
|
+
type RolePermissionsMap = Record<Role, readonly Permission[] | Permission[]>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default role-to-permissions map for Gatekeeper.
|
|
76
|
+
*
|
|
77
|
+
* - Admin: Full access across user management, reports, and settings.
|
|
78
|
+
* - Manager: Can read users and view reports.
|
|
79
|
+
* - Member: Can view reports.
|
|
80
|
+
*/
|
|
81
|
+
declare const DEFAULT_ROLE_PERMISSIONS: RolePermissionsMap;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a merged role-to-permissions map by extending or overriding the defaults.
|
|
84
|
+
*/
|
|
85
|
+
declare function defineRolePermissions(customMap?: Partial<RolePermissionsMap>, baseMap?: RolePermissionsMap): RolePermissionsMap;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes either a legacy RolePermissionsMap or a modern EvaluateOptions object.
|
|
89
|
+
* An empty plain object `{}` is treated as an explicit empty RolePermissionsMap (`{ roleMap: {} }`).
|
|
90
|
+
* When options is omitted/undefined, an empty options object is returned (roleMap remains undefined).
|
|
91
|
+
*/
|
|
92
|
+
declare function parseEvaluateOptions(options?: RoleMapOrOptions): EvaluateOptions;
|
|
93
|
+
/**
|
|
94
|
+
* Returns all effective permissions granted to a subject, combining
|
|
95
|
+
* permissions from every role in `roles`, the legacy `role` field,
|
|
96
|
+
* and direct permissions assigned in `permissions`.
|
|
97
|
+
* Deduplicates permissions and denies safely if no roles or permissions exist.
|
|
98
|
+
*/
|
|
99
|
+
declare function getUserEffectivePermissions(subject: AuthorizationSubject | null | undefined, roleMapOrOptions?: RoleMapOrOptions): Set<Permission>;
|
|
100
|
+
/**
|
|
101
|
+
* Evaluates whether a subject has a specific permission.
|
|
102
|
+
* Supports scoped policy checks and legacy roleMap options.
|
|
103
|
+
*
|
|
104
|
+
* @param subject - The subject to check.
|
|
105
|
+
* @param permission - The required permission string.
|
|
106
|
+
* @param options - Optional roleMap or EvaluateOptions ({ roleMap, context, policy }).
|
|
107
|
+
* @returns boolean indicating if access is granted.
|
|
108
|
+
*/
|
|
109
|
+
declare function can(subject: AuthorizationSubject | null | undefined, permission: Permission, options?: RoleMapOrOptions): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Checks if a subject has ALL of the requested permissions.
|
|
112
|
+
*/
|
|
113
|
+
declare function canAll(subject: AuthorizationSubject | null | undefined, permissions: Permission[], options?: RoleMapOrOptions): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Checks if a subject has AT LEAST ONE of the requested permissions.
|
|
116
|
+
*/
|
|
117
|
+
declare function canAny(subject: AuthorizationSubject | null | undefined, permissions: Permission[], options?: RoleMapOrOptions): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Evaluates access with detailed diagnostic reasons.
|
|
120
|
+
*/
|
|
121
|
+
declare function evaluatePermission(subject: AuthorizationSubject | null | undefined, permission: Permission, options?: RoleMapOrOptions): AuthorizationResult;
|
|
122
|
+
/**
|
|
123
|
+
* Normalizes feature flag inputs (either an array of FeatureFlag or a record of boolean flags)
|
|
124
|
+
* into a standard Record<string, boolean>.
|
|
125
|
+
*/
|
|
126
|
+
declare function normalizeFeatureFlags(flags?: Record<string, boolean> | FeatureFlag[]): Record<string, boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Checks whether a feature flag is enabled.
|
|
129
|
+
*/
|
|
130
|
+
declare function isFeatureEnabled(flags: Record<string, boolean> | FeatureFlag[] | undefined, flagKey: string): boolean;
|
|
131
|
+
|
|
132
|
+
interface FromRolesInput {
|
|
133
|
+
id: string;
|
|
134
|
+
role?: Role;
|
|
135
|
+
roles?: Role[] | Role;
|
|
136
|
+
permissions?: Permission[];
|
|
137
|
+
attributes?: Record<string, unknown>;
|
|
138
|
+
name?: string;
|
|
139
|
+
email?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes an identity with one or multiple roles into a standard AuthorizationSubject.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* // Single role
|
|
146
|
+
* fromRoles('u1', 'admin');
|
|
147
|
+
*
|
|
148
|
+
* // Multiple roles with metadata attributes
|
|
149
|
+
* fromRoles('u1', ['billing-manager', 'support-agent'], { tenantId: 't-123' });
|
|
150
|
+
*
|
|
151
|
+
* // Object input
|
|
152
|
+
* fromRoles({ id: 'u1', roles: ['editor', 'viewer'] });
|
|
153
|
+
*/
|
|
154
|
+
declare function fromRoles(idOrInput: string | FromRolesInput, roles?: Role[] | Role, attributes?: Record<string, unknown>): AuthorizationSubject;
|
|
155
|
+
interface FromPermissionsInput {
|
|
156
|
+
id: string;
|
|
157
|
+
permissions: Permission[];
|
|
158
|
+
attributes?: Record<string, unknown>;
|
|
159
|
+
name?: string;
|
|
160
|
+
email?: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Normalizes an identity with direct permissions into an AuthorizationSubject.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* fromPermissions('u1', ['users.read', 'reports.view']);
|
|
167
|
+
* fromPermissions({ id: 'u1', permissions: ['users.read'] });
|
|
168
|
+
*/
|
|
169
|
+
declare function fromPermissions(idOrInput: string | FromPermissionsInput, permissions?: Permission[], attributes?: Record<string, unknown>): AuthorizationSubject;
|
|
170
|
+
interface JwtClaimsOptions {
|
|
171
|
+
/** Field name(s) to check for subject ID. Defaults to ['sub', 'id'] */
|
|
172
|
+
idField?: string | string[];
|
|
173
|
+
/** Field name(s) or dot-paths to check for roles. Defaults to ['roles', 'role', 'realm_access.roles'] */
|
|
174
|
+
rolesField?: string | string[];
|
|
175
|
+
/** Field name(s) to check for permissions. Defaults to ['permissions', 'perms'] */
|
|
176
|
+
permissionsField?: string | string[];
|
|
177
|
+
/** Optional specific claim keys to copy into subject attributes */
|
|
178
|
+
attributesFields?: string[];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Normalizes JWT / OIDC claims payload into an AuthorizationSubject.
|
|
182
|
+
* Supports standard claims as well as Keycloak realm_access and custom claims.
|
|
183
|
+
*
|
|
184
|
+
* Note: This is a generic normalization helper and does not validate token signatures.
|
|
185
|
+
* Signature verification must be performed by your upstream authentication layer.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Keycloak JWT with realm_access.roles:
|
|
189
|
+
* const token = {
|
|
190
|
+
* sub: 'user_kc_123',
|
|
191
|
+
* realm_access: { roles: ['admin', 'billing'] },
|
|
192
|
+
* email: 'alex@example.com'
|
|
193
|
+
* };
|
|
194
|
+
* const subject = fromJwtClaims(token);
|
|
195
|
+
*/
|
|
196
|
+
declare function fromJwtClaims(claims: Record<string, unknown>, options?: JwtClaimsOptions): AuthorizationSubject;
|
|
197
|
+
interface SubjectMappingConfig<TSource> {
|
|
198
|
+
getId: (source: TSource) => string;
|
|
199
|
+
getRoles?: (source: TSource) => Role[] | Role | undefined | null;
|
|
200
|
+
getPermissions?: (source: TSource) => Permission[] | undefined | null;
|
|
201
|
+
getAttributes?: (source: TSource) => Record<string, unknown> | undefined | null;
|
|
202
|
+
getName?: (source: TSource) => string | undefined | null;
|
|
203
|
+
getEmail?: (source: TSource) => string | undefined | null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Creates an AuthorizationSubject by mapping any arbitrary domain or database model.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* // 1. Simple database record
|
|
210
|
+
* interface DbUser {
|
|
211
|
+
* user_id: string;
|
|
212
|
+
* primary_role: string;
|
|
213
|
+
* is_active: boolean;
|
|
214
|
+
* }
|
|
215
|
+
* const subject = createSubject(dbUser, {
|
|
216
|
+
* getId: (u) => u.user_id,
|
|
217
|
+
* getRoles: (u) => [u.primary_role],
|
|
218
|
+
* getAttributes: (u) => ({ isActive: u.is_active }),
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* // 2. Custom membership model with nested roles
|
|
222
|
+
* interface OrgMembership {
|
|
223
|
+
* memberId: string;
|
|
224
|
+
* organization: { orgId: string };
|
|
225
|
+
* access: { roles: string[]; customGrants?: string[] };
|
|
226
|
+
* }
|
|
227
|
+
* const memberSubject = createSubject(membership, {
|
|
228
|
+
* getId: (m) => m.memberId,
|
|
229
|
+
* getRoles: (m) => m.access.roles,
|
|
230
|
+
* getPermissions: (m) => m.access.customGrants,
|
|
231
|
+
* getAttributes: (m) => ({ orgId: m.organization.orgId }),
|
|
232
|
+
* });
|
|
233
|
+
*
|
|
234
|
+
* // 3. Functional transformer
|
|
235
|
+
* const customSubject = createSubject(rawInput, (input) => ({
|
|
236
|
+
* id: input.id,
|
|
237
|
+
* roles: input.assignedRoles,
|
|
238
|
+
* }));
|
|
239
|
+
*/
|
|
240
|
+
declare function createSubject<TSource>(source: TSource, mapping: SubjectMappingConfig<TSource> | ((source: TSource) => AuthorizationSubject)): AuthorizationSubject;
|
|
241
|
+
|
|
242
|
+
export { type AuthorizationPolicy, type AuthorizationResult, type AuthorizationSubject, DEFAULT_ROLE_PERMISSIONS, type EvaluateOptions, type FeatureFlag, type FromPermissionsInput, type FromRolesInput, type GatekeeperUser, type JwtClaimsOptions, type Permission, type Role, type RoleMapOrOptions, type RolePermissionsMap, type StandardPermission, type StandardRole, type SubjectMappingConfig, can, canAll, canAny, createSubject, defineRolePermissions, evaluatePermission, fromJwtClaims, fromPermissions, fromRoles, getUserEffectivePermissions, isFeatureEnabled, normalizeFeatureFlags, parseEvaluateOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/roles.ts
|
|
2
|
+
var DEFAULT_ROLE_PERMISSIONS = {
|
|
3
|
+
admin: ["users.read", "users.create", "users.delete", "reports.view", "settings.manage"],
|
|
4
|
+
manager: ["users.read", "reports.view"],
|
|
5
|
+
member: ["reports.view"]
|
|
6
|
+
};
|
|
7
|
+
function defineRolePermissions(customMap, baseMap = DEFAULT_ROLE_PERMISSIONS) {
|
|
8
|
+
if (!customMap) {
|
|
9
|
+
return { ...baseMap };
|
|
10
|
+
}
|
|
11
|
+
const merged = { ...baseMap };
|
|
12
|
+
for (const [role, perms] of Object.entries(customMap)) {
|
|
13
|
+
if (perms) {
|
|
14
|
+
merged[role] = [...perms];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return merged;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/evaluator.ts
|
|
21
|
+
function parseEvaluateOptions(options) {
|
|
22
|
+
if (!options || typeof options !== "object") {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
if ("roleMap" in options || "context" in options || "policy" in options) {
|
|
26
|
+
return options;
|
|
27
|
+
}
|
|
28
|
+
const values = Object.values(options);
|
|
29
|
+
if (values.length === 0 || values.every((v) => Array.isArray(v))) {
|
|
30
|
+
return { roleMap: options };
|
|
31
|
+
}
|
|
32
|
+
return options;
|
|
33
|
+
}
|
|
34
|
+
function getUserEffectivePermissions(subject, roleMapOrOptions) {
|
|
35
|
+
const permissions = /* @__PURE__ */ new Set();
|
|
36
|
+
if (!subject) {
|
|
37
|
+
return permissions;
|
|
38
|
+
}
|
|
39
|
+
const opts = parseEvaluateOptions(roleMapOrOptions);
|
|
40
|
+
const roleMap = opts.roleMap ?? DEFAULT_ROLE_PERMISSIONS;
|
|
41
|
+
const rolesToEvaluate = /* @__PURE__ */ new Set();
|
|
42
|
+
if (Array.isArray(subject.roles)) {
|
|
43
|
+
for (const r of subject.roles) {
|
|
44
|
+
if (r) rolesToEvaluate.add(r);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (subject.role) {
|
|
48
|
+
rolesToEvaluate.add(subject.role);
|
|
49
|
+
}
|
|
50
|
+
for (const role of rolesToEvaluate) {
|
|
51
|
+
const rolePerms = roleMap[role];
|
|
52
|
+
if (rolePerms && Array.isArray(rolePerms)) {
|
|
53
|
+
for (const perm of rolePerms) {
|
|
54
|
+
permissions.add(perm);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(subject.permissions)) {
|
|
59
|
+
for (const perm of subject.permissions) {
|
|
60
|
+
if (perm) permissions.add(perm);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return permissions;
|
|
64
|
+
}
|
|
65
|
+
function can(subject, permission, options) {
|
|
66
|
+
if (!subject || !permission) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const opts = parseEvaluateOptions(options);
|
|
70
|
+
if (opts.policy) {
|
|
71
|
+
const decision = opts.policy(subject, permission, opts.context);
|
|
72
|
+
if (typeof decision === "boolean") {
|
|
73
|
+
return decision;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const effective = getUserEffectivePermissions(subject, options !== void 0 ? opts : void 0);
|
|
77
|
+
return effective.has(permission);
|
|
78
|
+
}
|
|
79
|
+
function canAll(subject, permissions, options) {
|
|
80
|
+
if (!subject || !permissions.length) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return permissions.every((p) => can(subject, p, options));
|
|
84
|
+
}
|
|
85
|
+
function canAny(subject, permissions, options) {
|
|
86
|
+
if (!subject || !permissions.length) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return permissions.some((p) => can(subject, p, options));
|
|
90
|
+
}
|
|
91
|
+
function evaluatePermission(subject, permission, options) {
|
|
92
|
+
if (!subject) {
|
|
93
|
+
return {
|
|
94
|
+
allowed: false,
|
|
95
|
+
reason: "No authenticated user provided.",
|
|
96
|
+
missingPermissions: [permission]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const opts = parseEvaluateOptions(options);
|
|
100
|
+
if (opts.policy) {
|
|
101
|
+
const decision = opts.policy(subject, permission, opts.context);
|
|
102
|
+
if (typeof decision === "boolean") {
|
|
103
|
+
return {
|
|
104
|
+
allowed: decision,
|
|
105
|
+
reason: decision ? "Permission granted by policy." : "Permission denied by policy.",
|
|
106
|
+
missingPermissions: decision ? void 0 : [permission]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const effective = getUserEffectivePermissions(subject, options !== void 0 ? opts : void 0);
|
|
111
|
+
const allowed = effective.has(permission);
|
|
112
|
+
if (allowed) {
|
|
113
|
+
return {
|
|
114
|
+
allowed: true,
|
|
115
|
+
reason: "Permission granted."
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const roleDesc = subject.role ?? (subject.roles?.length ? subject.roles.join(", ") : "none");
|
|
119
|
+
return {
|
|
120
|
+
allowed: false,
|
|
121
|
+
reason: `User with role '${roleDesc}' lacks required permission '${permission}'.`,
|
|
122
|
+
missingPermissions: [permission]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function normalizeFeatureFlags(flags) {
|
|
126
|
+
if (!flags) {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(flags)) {
|
|
130
|
+
const result = {};
|
|
131
|
+
for (const flag of flags) {
|
|
132
|
+
result[flag.key] = flag.enabled;
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
return { ...flags };
|
|
137
|
+
}
|
|
138
|
+
function isFeatureEnabled(flags, flagKey) {
|
|
139
|
+
if (!flags || !flagKey) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(flags)) {
|
|
143
|
+
const found = flags.find((f) => f.key === flagKey);
|
|
144
|
+
return Boolean(found?.enabled);
|
|
145
|
+
}
|
|
146
|
+
return Boolean(flags[flagKey]);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/adapters.ts
|
|
150
|
+
function fromRoles(idOrInput, roles, attributes) {
|
|
151
|
+
if (typeof idOrInput === "object" && idOrInput !== null) {
|
|
152
|
+
const rolesList2 = [];
|
|
153
|
+
if (Array.isArray(idOrInput.roles)) {
|
|
154
|
+
rolesList2.push(...idOrInput.roles);
|
|
155
|
+
} else if (idOrInput.roles) {
|
|
156
|
+
rolesList2.push(idOrInput.roles);
|
|
157
|
+
}
|
|
158
|
+
if (idOrInput.role && !rolesList2.includes(idOrInput.role)) {
|
|
159
|
+
rolesList2.push(idOrInput.role);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
id: String(idOrInput.id),
|
|
163
|
+
roles: rolesList2,
|
|
164
|
+
role: idOrInput.role ?? rolesList2[0],
|
|
165
|
+
...idOrInput.permissions ? { permissions: idOrInput.permissions } : {},
|
|
166
|
+
...idOrInput.attributes ? { attributes: idOrInput.attributes } : {},
|
|
167
|
+
...idOrInput.name ? { name: idOrInput.name } : {},
|
|
168
|
+
...idOrInput.email ? { email: idOrInput.email } : {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const rolesList = [];
|
|
172
|
+
if (Array.isArray(roles)) {
|
|
173
|
+
rolesList.push(...roles);
|
|
174
|
+
} else if (roles) {
|
|
175
|
+
rolesList.push(roles);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: String(idOrInput),
|
|
179
|
+
roles: rolesList,
|
|
180
|
+
role: rolesList[0],
|
|
181
|
+
...attributes ? { attributes } : {}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function fromPermissions(idOrInput, permissions, attributes) {
|
|
185
|
+
if (typeof idOrInput === "object" && idOrInput !== null) {
|
|
186
|
+
return {
|
|
187
|
+
id: String(idOrInput.id),
|
|
188
|
+
roles: [],
|
|
189
|
+
permissions: Array.isArray(idOrInput.permissions) ? [...idOrInput.permissions] : [],
|
|
190
|
+
...idOrInput.attributes ? { attributes: idOrInput.attributes } : {},
|
|
191
|
+
...idOrInput.name ? { name: idOrInput.name } : {},
|
|
192
|
+
...idOrInput.email ? { email: idOrInput.email } : {}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
id: String(idOrInput),
|
|
197
|
+
roles: [],
|
|
198
|
+
permissions: Array.isArray(permissions) ? [...permissions] : [],
|
|
199
|
+
...attributes ? { attributes } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getByPath(obj, path) {
|
|
203
|
+
const parts = path.split(".");
|
|
204
|
+
let current = obj;
|
|
205
|
+
for (const part of parts) {
|
|
206
|
+
if (current && typeof current === "object" && part in current) {
|
|
207
|
+
current = current[part];
|
|
208
|
+
} else {
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return current;
|
|
213
|
+
}
|
|
214
|
+
function fromJwtClaims(claims, options = {}) {
|
|
215
|
+
if (!claims || typeof claims !== "object") {
|
|
216
|
+
return { id: "", roles: [], permissions: [] };
|
|
217
|
+
}
|
|
218
|
+
const idFields = options.idField ? Array.isArray(options.idField) ? options.idField : [options.idField] : ["sub", "id"];
|
|
219
|
+
let resolvedId = "";
|
|
220
|
+
for (const field of idFields) {
|
|
221
|
+
const val = getByPath(claims, field);
|
|
222
|
+
if (val !== void 0 && val !== null && String(val).trim()) {
|
|
223
|
+
resolvedId = String(val);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const rolesFields = options.rolesField ? Array.isArray(options.rolesField) ? options.rolesField : [options.rolesField] : ["roles", "role", "realm_access.roles"];
|
|
228
|
+
const resolvedRoles = [];
|
|
229
|
+
for (const field of rolesFields) {
|
|
230
|
+
const val = getByPath(claims, field);
|
|
231
|
+
if (Array.isArray(val)) {
|
|
232
|
+
for (const item of val) {
|
|
233
|
+
if (typeof item === "string") {
|
|
234
|
+
resolvedRoles.push(item);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (resolvedRoles.length > 0) break;
|
|
238
|
+
} else if (typeof val === "string" && val.trim()) {
|
|
239
|
+
resolvedRoles.push(val);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const permFields = options.permissionsField ? Array.isArray(options.permissionsField) ? options.permissionsField : [options.permissionsField] : ["permissions", "perms"];
|
|
244
|
+
const resolvedPerms = [];
|
|
245
|
+
for (const field of permFields) {
|
|
246
|
+
const val = getByPath(claims, field);
|
|
247
|
+
if (Array.isArray(val)) {
|
|
248
|
+
for (const item of val) {
|
|
249
|
+
if (typeof item === "string") {
|
|
250
|
+
resolvedPerms.push(item);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (resolvedPerms.length > 0) break;
|
|
254
|
+
} else if (typeof val === "string" && val.trim()) {
|
|
255
|
+
resolvedPerms.push(val);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const attributes = {};
|
|
260
|
+
if (options.attributesFields && Array.isArray(options.attributesFields)) {
|
|
261
|
+
for (const attrField of options.attributesFields) {
|
|
262
|
+
const val = getByPath(claims, attrField);
|
|
263
|
+
if (val !== void 0) {
|
|
264
|
+
attributes[attrField] = val;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
const excludedKeys = /* @__PURE__ */ new Set([
|
|
269
|
+
"sub",
|
|
270
|
+
"id",
|
|
271
|
+
"roles",
|
|
272
|
+
"role",
|
|
273
|
+
"realm_access",
|
|
274
|
+
"permissions",
|
|
275
|
+
"perms"
|
|
276
|
+
]);
|
|
277
|
+
for (const [k, v] of Object.entries(claims)) {
|
|
278
|
+
if (!excludedKeys.has(k) && v !== void 0) {
|
|
279
|
+
attributes[k] = v;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const email = typeof claims.email === "string" ? claims.email : void 0;
|
|
284
|
+
const name = typeof claims.name === "string" ? claims.name : typeof claims.preferred_username === "string" ? claims.preferred_username : void 0;
|
|
285
|
+
return {
|
|
286
|
+
id: resolvedId,
|
|
287
|
+
roles: resolvedRoles,
|
|
288
|
+
role: resolvedRoles[0],
|
|
289
|
+
permissions: resolvedPerms,
|
|
290
|
+
attributes,
|
|
291
|
+
...name ? { name } : {},
|
|
292
|
+
...email ? { email } : {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function createSubject(source, mapping) {
|
|
296
|
+
if (typeof mapping === "function") {
|
|
297
|
+
return mapping(source);
|
|
298
|
+
}
|
|
299
|
+
const rawRoles = mapping.getRoles ? mapping.getRoles(source) : void 0;
|
|
300
|
+
const rolesList = [];
|
|
301
|
+
if (Array.isArray(rawRoles)) {
|
|
302
|
+
for (const r of rawRoles) {
|
|
303
|
+
if (r) rolesList.push(r);
|
|
304
|
+
}
|
|
305
|
+
} else if (rawRoles) {
|
|
306
|
+
rolesList.push(rawRoles);
|
|
307
|
+
}
|
|
308
|
+
const rawPerms = mapping.getPermissions ? mapping.getPermissions(source) : void 0;
|
|
309
|
+
const permsList = Array.isArray(rawPerms) ? [...rawPerms] : [];
|
|
310
|
+
const attributes = mapping.getAttributes ? mapping.getAttributes(source) : void 0;
|
|
311
|
+
const name = mapping.getName ? mapping.getName(source) : void 0;
|
|
312
|
+
const email = mapping.getEmail ? mapping.getEmail(source) : void 0;
|
|
313
|
+
return {
|
|
314
|
+
id: mapping.getId(source),
|
|
315
|
+
roles: rolesList,
|
|
316
|
+
role: rolesList[0],
|
|
317
|
+
permissions: permsList,
|
|
318
|
+
...attributes ? { attributes } : {},
|
|
319
|
+
...name ? { name } : {},
|
|
320
|
+
...email ? { email } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
DEFAULT_ROLE_PERMISSIONS,
|
|
325
|
+
can,
|
|
326
|
+
canAll,
|
|
327
|
+
canAny,
|
|
328
|
+
createSubject,
|
|
329
|
+
defineRolePermissions,
|
|
330
|
+
evaluatePermission,
|
|
331
|
+
fromJwtClaims,
|
|
332
|
+
fromPermissions,
|
|
333
|
+
fromRoles,
|
|
334
|
+
getUserEffectivePermissions,
|
|
335
|
+
isFeatureEnabled,
|
|
336
|
+
normalizeFeatureFlags,
|
|
337
|
+
parseEvaluateOptions
|
|
338
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahmed109/gatekeeper-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pure framework-independent authorization and feature-flag evaluator for TypeScript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"authorization",
|
|
7
|
+
"rbac",
|
|
8
|
+
"abac",
|
|
9
|
+
"permissions",
|
|
10
|
+
"feature-flags",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"author": "Ahmed Tamer",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/Ahmed1092002/npm-packages.git",
|
|
18
|
+
"directory": "gatekeeper/packages/core"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/Ahmed1092002/npm-packages/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/Ahmed1092002/npm-packages/tree/main/gatekeeper#readme",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"tsup": "^8.3.6",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "^3.0.5"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
50
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"lint": "eslint src"
|
|
53
|
+
}
|
|
6
54
|
}
|