@rbacbee-lib/core 0.1.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/README.md +20 -0
- package/dist/index.cjs +629 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +579 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @rbacbee-lib/core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic authorization engine for RBAC, scoped permissions and policy evaluation.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createRbacEngine } from '@rbacbee-lib/core';
|
|
7
|
+
|
|
8
|
+
const engine = createRbacEngine({ accessRepository });
|
|
9
|
+
|
|
10
|
+
const result = await engine.can({ userId: 'user-1' }, 'posts:update', {
|
|
11
|
+
tenantId: 'tenant-1'
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Security defaults:
|
|
16
|
+
|
|
17
|
+
- Deny by default.
|
|
18
|
+
- No implicit user storage assumptions.
|
|
19
|
+
- Input validation through value objects.
|
|
20
|
+
- Storage and cache are ports, not framework dependencies.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
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
|
+
Assignment: () => Assignment,
|
|
24
|
+
CheckPermissionUseCase: () => CheckPermissionUseCase,
|
|
25
|
+
CheckRoleUseCase: () => CheckRoleUseCase,
|
|
26
|
+
DefaultRbacEngine: () => DefaultRbacEngine,
|
|
27
|
+
MemoryCache: () => MemoryCache,
|
|
28
|
+
Permission: () => Permission,
|
|
29
|
+
PermissionEvaluator: () => PermissionEvaluator,
|
|
30
|
+
PermissionKey: () => PermissionKey,
|
|
31
|
+
PolicyKey: () => PolicyKey,
|
|
32
|
+
PolicyRegistry: () => PolicyRegistry,
|
|
33
|
+
RbacAuthorizationError: () => RbacAuthorizationError,
|
|
34
|
+
RbacConfigurationError: () => RbacConfigurationError,
|
|
35
|
+
RbacError: () => RbacError,
|
|
36
|
+
RbacValidationError: () => RbacValidationError,
|
|
37
|
+
ResourceId: () => ResourceId,
|
|
38
|
+
Role: () => Role,
|
|
39
|
+
RoleId: () => RoleId,
|
|
40
|
+
SystemClock: () => SystemClock,
|
|
41
|
+
TenantId: () => TenantId,
|
|
42
|
+
UserId: () => UserId,
|
|
43
|
+
accessProfileCacheKey: () => accessProfileCacheKey,
|
|
44
|
+
createRbacEngine: () => createRbacEngine,
|
|
45
|
+
matchesPermission: () => matchesPermission,
|
|
46
|
+
normalizeContext: () => normalizeContext
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/domain/errors/rbac-error.ts
|
|
51
|
+
var RbacError = class extends Error {
|
|
52
|
+
code;
|
|
53
|
+
constructor(code, message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = new.target.name;
|
|
56
|
+
this.code = code;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var RbacValidationError = class extends RbacError {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super("RBAC_VALIDATION_ERROR", message);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var RbacAuthorizationError = class extends RbacError {
|
|
65
|
+
constructor(message = "Authorization denied") {
|
|
66
|
+
super("RBAC_AUTHORIZATION_DENIED", message);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var RbacConfigurationError = class extends RbacError {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super("RBAC_CONFIGURATION_ERROR", message);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/domain/value-objects/identifiers.ts
|
|
76
|
+
var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_:@./-]{0,255}$/;
|
|
77
|
+
var PERMISSION_PATTERN = /^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$/;
|
|
78
|
+
var StringValueObject = class {
|
|
79
|
+
value;
|
|
80
|
+
constructor(value, name, pattern, maxLength) {
|
|
81
|
+
const normalized = value.trim();
|
|
82
|
+
if (normalized.length === 0) {
|
|
83
|
+
throw new RbacValidationError(`${name} cannot be empty`);
|
|
84
|
+
}
|
|
85
|
+
if (normalized.length > maxLength) {
|
|
86
|
+
throw new RbacValidationError(`${name} cannot exceed ${maxLength} characters`);
|
|
87
|
+
}
|
|
88
|
+
if (!pattern.test(normalized)) {
|
|
89
|
+
throw new RbacValidationError(`${name} contains invalid characters`);
|
|
90
|
+
}
|
|
91
|
+
this.value = normalized;
|
|
92
|
+
Object.freeze(this);
|
|
93
|
+
}
|
|
94
|
+
equals(other) {
|
|
95
|
+
return this.value === other.value;
|
|
96
|
+
}
|
|
97
|
+
toString() {
|
|
98
|
+
return this.value;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var UserId = class _UserId extends StringValueObject {
|
|
102
|
+
constructor(value) {
|
|
103
|
+
super(value, "UserId", ID_PATTERN, 256);
|
|
104
|
+
}
|
|
105
|
+
static create(value) {
|
|
106
|
+
return new _UserId(value);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var RoleId = class _RoleId extends StringValueObject {
|
|
110
|
+
constructor(value) {
|
|
111
|
+
super(value, "RoleId", ID_PATTERN, 256);
|
|
112
|
+
}
|
|
113
|
+
static create(value) {
|
|
114
|
+
return new _RoleId(value);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var TenantId = class _TenantId extends StringValueObject {
|
|
118
|
+
constructor(value) {
|
|
119
|
+
super(value, "TenantId", ID_PATTERN, 256);
|
|
120
|
+
}
|
|
121
|
+
static create(value) {
|
|
122
|
+
return new _TenantId(value);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var ResourceId = class _ResourceId extends StringValueObject {
|
|
126
|
+
constructor(value) {
|
|
127
|
+
super(value, "ResourceId", ID_PATTERN, 256);
|
|
128
|
+
}
|
|
129
|
+
static create(value) {
|
|
130
|
+
return new _ResourceId(value);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var PermissionKey = class _PermissionKey extends StringValueObject {
|
|
134
|
+
constructor(value) {
|
|
135
|
+
super(value, "PermissionKey", PERMISSION_PATTERN, 128);
|
|
136
|
+
}
|
|
137
|
+
static create(value) {
|
|
138
|
+
return new _PermissionKey(value);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var PolicyKey = class _PolicyKey extends StringValueObject {
|
|
142
|
+
constructor(value) {
|
|
143
|
+
super(value, "PolicyKey", PERMISSION_PATTERN, 128);
|
|
144
|
+
}
|
|
145
|
+
static create(value) {
|
|
146
|
+
return new _PolicyKey(value);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/domain/policies/policy-registry.ts
|
|
151
|
+
var PolicyRegistry = class {
|
|
152
|
+
handlers = /* @__PURE__ */ new Map();
|
|
153
|
+
register(policy, handler) {
|
|
154
|
+
const key = PolicyKey.create(policy).value;
|
|
155
|
+
this.handlers.set(key, handler);
|
|
156
|
+
}
|
|
157
|
+
has(policy) {
|
|
158
|
+
return this.handlers.has(PolicyKey.create(policy).value);
|
|
159
|
+
}
|
|
160
|
+
async evaluate(policy, principal, context) {
|
|
161
|
+
const key = PolicyKey.create(policy).value;
|
|
162
|
+
const handler = this.handlers.get(key);
|
|
163
|
+
if (handler === void 0) {
|
|
164
|
+
return {
|
|
165
|
+
decision: "deny",
|
|
166
|
+
reason: "policy not registered",
|
|
167
|
+
principal,
|
|
168
|
+
context,
|
|
169
|
+
policy: key
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const result = await handler({ principal, context });
|
|
173
|
+
if (typeof result === "boolean") {
|
|
174
|
+
return {
|
|
175
|
+
decision: result ? "allow" : "deny",
|
|
176
|
+
reason: result ? "policy allowed" : "policy denied",
|
|
177
|
+
principal,
|
|
178
|
+
context,
|
|
179
|
+
policy: key
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/infrastructure/clock/system-clock.ts
|
|
187
|
+
var SystemClock = class {
|
|
188
|
+
now() {
|
|
189
|
+
return /* @__PURE__ */ new Date();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/domain/services/permission-evaluator.ts
|
|
194
|
+
var PermissionEvaluator = class {
|
|
195
|
+
evaluatePermission(profile, principal, requiredPermission, context) {
|
|
196
|
+
const now = context.now ?? /* @__PURE__ */ new Date();
|
|
197
|
+
const matched = profile.permissions.filter((permission) => permissionApplies(permission, requiredPermission, context, now)).map((permission) => permission.key);
|
|
198
|
+
if (matched.length > 0) {
|
|
199
|
+
return {
|
|
200
|
+
decision: "allow",
|
|
201
|
+
reason: "permission matched",
|
|
202
|
+
principal,
|
|
203
|
+
context,
|
|
204
|
+
permission: requiredPermission,
|
|
205
|
+
matched
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
decision: "deny",
|
|
210
|
+
reason: "permission not found",
|
|
211
|
+
principal,
|
|
212
|
+
context,
|
|
213
|
+
permission: requiredPermission
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
evaluateRole(profile, principal, requiredRole, context) {
|
|
217
|
+
const now = context.now ?? /* @__PURE__ */ new Date();
|
|
218
|
+
const matched = profile.roles.filter((role) => roleApplies(role, requiredRole, context, now)).map((role) => role.id);
|
|
219
|
+
if (matched.length > 0) {
|
|
220
|
+
return {
|
|
221
|
+
decision: "allow",
|
|
222
|
+
reason: "role matched",
|
|
223
|
+
principal,
|
|
224
|
+
context,
|
|
225
|
+
role: requiredRole,
|
|
226
|
+
matched
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
decision: "deny",
|
|
231
|
+
reason: "role not found",
|
|
232
|
+
principal,
|
|
233
|
+
context,
|
|
234
|
+
role: requiredRole
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function matchesPermission(granted, required) {
|
|
239
|
+
if (granted === "*" || granted === required) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
const grantedParts = granted.split(":");
|
|
243
|
+
const requiredParts = required.split(":");
|
|
244
|
+
for (let index = 0; index < grantedParts.length; index += 1) {
|
|
245
|
+
const grantedPart = grantedParts[index];
|
|
246
|
+
const requiredPart = requiredParts[index];
|
|
247
|
+
if (grantedPart === void 0) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (grantedPart === "*") {
|
|
251
|
+
return index === grantedParts.length - 1 || requiredPart !== void 0;
|
|
252
|
+
}
|
|
253
|
+
if (requiredPart === void 0 || grantedPart !== requiredPart) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return grantedParts.length === requiredParts.length;
|
|
258
|
+
}
|
|
259
|
+
function permissionApplies(granted, requiredPermission, context, now) {
|
|
260
|
+
if (granted.expiresAt !== void 0 && granted.expiresAt <= now) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (!scopeApplies(granted, context)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return matchesPermission(granted.key, requiredPermission);
|
|
267
|
+
}
|
|
268
|
+
function roleApplies(granted, requiredRole, context, now) {
|
|
269
|
+
if (granted.expiresAt !== void 0 && granted.expiresAt <= now) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (!scopeApplies(granted, context)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return granted.id === requiredRole || granted.name === requiredRole;
|
|
276
|
+
}
|
|
277
|
+
function scopeApplies(granted, context) {
|
|
278
|
+
if (granted.tenantId !== void 0 && granted.tenantId !== context.tenantId) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (granted.resourceType !== void 0 && granted.resourceType !== context.resourceType) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (granted.resourceId !== void 0 && granted.resourceId !== context.resourceId) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/application/use-cases/check-permission.ts
|
|
291
|
+
var CheckPermissionUseCase = class {
|
|
292
|
+
accessRepository;
|
|
293
|
+
evaluator;
|
|
294
|
+
cache;
|
|
295
|
+
audit;
|
|
296
|
+
clock;
|
|
297
|
+
cacheTtlMs;
|
|
298
|
+
constructor(dependencies) {
|
|
299
|
+
this.accessRepository = dependencies.accessRepository;
|
|
300
|
+
this.evaluator = dependencies.evaluator ?? new PermissionEvaluator();
|
|
301
|
+
this.cache = dependencies.cache;
|
|
302
|
+
this.audit = dependencies.audit;
|
|
303
|
+
this.clock = dependencies.clock ?? new SystemClock();
|
|
304
|
+
this.cacheTtlMs = dependencies.cacheTtlMs;
|
|
305
|
+
}
|
|
306
|
+
async execute(principal, requiredPermission, context = {}) {
|
|
307
|
+
UserId.create(principal.userId);
|
|
308
|
+
PermissionKey.create(requiredPermission);
|
|
309
|
+
const normalizedContext = normalizeContext(principal, context, this.clock.now());
|
|
310
|
+
const profile = await this.loadProfile(principal, normalizedContext);
|
|
311
|
+
const result = profile === null ? deny(principal, normalizedContext, requiredPermission, "access profile not found") : this.evaluator.evaluatePermission(
|
|
312
|
+
profile,
|
|
313
|
+
principal,
|
|
314
|
+
requiredPermission,
|
|
315
|
+
normalizedContext
|
|
316
|
+
);
|
|
317
|
+
await this.audit?.record({
|
|
318
|
+
type: "authorization.checked",
|
|
319
|
+
occurredAt: this.clock.now(),
|
|
320
|
+
decision: result.decision,
|
|
321
|
+
principal,
|
|
322
|
+
permission: requiredPermission,
|
|
323
|
+
context: normalizedContext,
|
|
324
|
+
reason: result.reason
|
|
325
|
+
});
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
async loadProfile(principal, context) {
|
|
329
|
+
const cacheKey = accessProfileCacheKey(principal.userId, context.tenantId);
|
|
330
|
+
const cachedProfile = await this.cache?.get(cacheKey);
|
|
331
|
+
if (cachedProfile !== void 0) {
|
|
332
|
+
return cachedProfile;
|
|
333
|
+
}
|
|
334
|
+
const query = { userId: principal.userId };
|
|
335
|
+
if (context.tenantId !== void 0) {
|
|
336
|
+
TenantId.create(context.tenantId);
|
|
337
|
+
Object.assign(query, { tenantId: context.tenantId });
|
|
338
|
+
}
|
|
339
|
+
const profile = await this.accessRepository.getAccessProfile(query);
|
|
340
|
+
if (profile !== null) {
|
|
341
|
+
await this.cache?.set(cacheKey, profile, this.cacheTtlMs);
|
|
342
|
+
}
|
|
343
|
+
return profile;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
function normalizeContext(principal, context, now) {
|
|
347
|
+
return {
|
|
348
|
+
...context,
|
|
349
|
+
tenantId: context.tenantId ?? principal.tenantId,
|
|
350
|
+
now: context.now ?? now
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function accessProfileCacheKey(userId, tenantId) {
|
|
354
|
+
return `rbac:access-profile:${userId}:${tenantId ?? "global"}`;
|
|
355
|
+
}
|
|
356
|
+
function deny(principal, context, permission, reason) {
|
|
357
|
+
return {
|
|
358
|
+
decision: "deny",
|
|
359
|
+
reason,
|
|
360
|
+
principal,
|
|
361
|
+
context,
|
|
362
|
+
permission
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/application/use-cases/check-role.ts
|
|
367
|
+
var CheckRoleUseCase = class {
|
|
368
|
+
accessRepository;
|
|
369
|
+
evaluator;
|
|
370
|
+
cache;
|
|
371
|
+
audit;
|
|
372
|
+
clock;
|
|
373
|
+
cacheTtlMs;
|
|
374
|
+
constructor(dependencies) {
|
|
375
|
+
this.accessRepository = dependencies.accessRepository;
|
|
376
|
+
this.evaluator = dependencies.evaluator ?? new PermissionEvaluator();
|
|
377
|
+
this.cache = dependencies.cache;
|
|
378
|
+
this.audit = dependencies.audit;
|
|
379
|
+
this.clock = dependencies.clock ?? new SystemClock();
|
|
380
|
+
this.cacheTtlMs = dependencies.cacheTtlMs;
|
|
381
|
+
}
|
|
382
|
+
async execute(principal, requiredRole, context = {}) {
|
|
383
|
+
UserId.create(principal.userId);
|
|
384
|
+
RoleId.create(requiredRole);
|
|
385
|
+
const normalizedContext = normalizeContext(principal, context, this.clock.now());
|
|
386
|
+
const profile = await this.loadProfile(principal, normalizedContext);
|
|
387
|
+
const result = profile === null ? deny2(principal, normalizedContext, requiredRole, "access profile not found") : this.evaluator.evaluateRole(profile, principal, requiredRole, normalizedContext);
|
|
388
|
+
await this.audit?.record({
|
|
389
|
+
type: "role.checked",
|
|
390
|
+
occurredAt: this.clock.now(),
|
|
391
|
+
decision: result.decision,
|
|
392
|
+
principal,
|
|
393
|
+
role: requiredRole,
|
|
394
|
+
context: normalizedContext,
|
|
395
|
+
reason: result.reason
|
|
396
|
+
});
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
async loadProfile(principal, context) {
|
|
400
|
+
const cacheKey = accessProfileCacheKey(principal.userId, context.tenantId);
|
|
401
|
+
const cachedProfile = await this.cache?.get(cacheKey);
|
|
402
|
+
if (cachedProfile !== void 0) {
|
|
403
|
+
return cachedProfile;
|
|
404
|
+
}
|
|
405
|
+
const query = { userId: principal.userId };
|
|
406
|
+
if (context.tenantId !== void 0) {
|
|
407
|
+
TenantId.create(context.tenantId);
|
|
408
|
+
Object.assign(query, { tenantId: context.tenantId });
|
|
409
|
+
}
|
|
410
|
+
const profile = await this.accessRepository.getAccessProfile(query);
|
|
411
|
+
if (profile !== null) {
|
|
412
|
+
await this.cache?.set(cacheKey, profile, this.cacheTtlMs);
|
|
413
|
+
}
|
|
414
|
+
return profile;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
function deny2(principal, context, role, reason) {
|
|
418
|
+
return {
|
|
419
|
+
decision: "deny",
|
|
420
|
+
reason,
|
|
421
|
+
principal,
|
|
422
|
+
context,
|
|
423
|
+
role
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/application/rbac-engine.ts
|
|
428
|
+
var DefaultRbacEngine = class {
|
|
429
|
+
checkPermission;
|
|
430
|
+
checkRole;
|
|
431
|
+
policies;
|
|
432
|
+
audit;
|
|
433
|
+
clock;
|
|
434
|
+
constructor(dependencies) {
|
|
435
|
+
this.clock = dependencies.clock ?? new SystemClock();
|
|
436
|
+
this.audit = dependencies.audit;
|
|
437
|
+
this.policies = dependencies.policies ?? new PolicyRegistry();
|
|
438
|
+
this.checkPermission = new CheckPermissionUseCase(dependencies);
|
|
439
|
+
this.checkRole = new CheckRoleUseCase(dependencies);
|
|
440
|
+
}
|
|
441
|
+
can(principal, permission, context = {}) {
|
|
442
|
+
return this.checkPermission.execute(principal, permission, context);
|
|
443
|
+
}
|
|
444
|
+
async assertCan(principal, permission, context = {}) {
|
|
445
|
+
const result = await this.can(principal, permission, context);
|
|
446
|
+
if (result.decision === "deny") {
|
|
447
|
+
throw new RbacAuthorizationError(result.reason);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
hasRole(principal, role, context = {}) {
|
|
451
|
+
return this.checkRole.execute(principal, role, context);
|
|
452
|
+
}
|
|
453
|
+
async evaluatePolicy(policy, principal, context = {}) {
|
|
454
|
+
const key = PolicyKey.create(policy).value;
|
|
455
|
+
const result = await this.policies.evaluate(key, principal, {
|
|
456
|
+
...context,
|
|
457
|
+
tenantId: context.tenantId ?? principal.tenantId,
|
|
458
|
+
now: context.now ?? this.clock.now()
|
|
459
|
+
});
|
|
460
|
+
await this.audit?.record({
|
|
461
|
+
type: "policy.checked",
|
|
462
|
+
occurredAt: this.clock.now(),
|
|
463
|
+
decision: result.decision,
|
|
464
|
+
principal,
|
|
465
|
+
policy: key,
|
|
466
|
+
context: result.context,
|
|
467
|
+
reason: result.reason
|
|
468
|
+
});
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
function createRbacEngine(dependencies) {
|
|
473
|
+
return new DefaultRbacEngine(dependencies);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/domain/entities/assignment.ts
|
|
477
|
+
var Assignment = class _Assignment {
|
|
478
|
+
userId;
|
|
479
|
+
roleId;
|
|
480
|
+
tenantId;
|
|
481
|
+
resourceType;
|
|
482
|
+
resourceId;
|
|
483
|
+
expiresAt;
|
|
484
|
+
constructor(input) {
|
|
485
|
+
this.userId = UserId.create(input.userId);
|
|
486
|
+
this.roleId = RoleId.create(input.roleId);
|
|
487
|
+
this.tenantId = input.tenantId === void 0 ? void 0 : TenantId.create(input.tenantId);
|
|
488
|
+
this.resourceType = input.resourceType;
|
|
489
|
+
this.resourceId = input.resourceId === void 0 ? void 0 : ResourceId.create(input.resourceId);
|
|
490
|
+
this.expiresAt = input.expiresAt;
|
|
491
|
+
Object.freeze(this);
|
|
492
|
+
}
|
|
493
|
+
static create(input) {
|
|
494
|
+
return new _Assignment(input);
|
|
495
|
+
}
|
|
496
|
+
toPrimitives() {
|
|
497
|
+
const output = {
|
|
498
|
+
userId: this.userId.value,
|
|
499
|
+
roleId: this.roleId.value
|
|
500
|
+
};
|
|
501
|
+
return {
|
|
502
|
+
...output,
|
|
503
|
+
...this.tenantId === void 0 ? {} : { tenantId: this.tenantId.value },
|
|
504
|
+
...this.resourceType === void 0 ? {} : { resourceType: this.resourceType },
|
|
505
|
+
...this.resourceId === void 0 ? {} : { resourceId: this.resourceId.value },
|
|
506
|
+
...this.expiresAt === void 0 ? {} : { expiresAt: this.expiresAt }
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// src/domain/entities/permission.ts
|
|
512
|
+
var Permission = class _Permission {
|
|
513
|
+
key;
|
|
514
|
+
description;
|
|
515
|
+
constructor(key, description) {
|
|
516
|
+
this.key = key;
|
|
517
|
+
this.description = description;
|
|
518
|
+
Object.freeze(this);
|
|
519
|
+
}
|
|
520
|
+
static create(input) {
|
|
521
|
+
return new _Permission(PermissionKey.create(input.key), input.description);
|
|
522
|
+
}
|
|
523
|
+
toPrimitives() {
|
|
524
|
+
const output = { key: this.key.value };
|
|
525
|
+
if (this.description !== void 0) {
|
|
526
|
+
return { ...output, description: this.description };
|
|
527
|
+
}
|
|
528
|
+
return output;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/domain/entities/role.ts
|
|
533
|
+
var Role = class _Role {
|
|
534
|
+
id;
|
|
535
|
+
name;
|
|
536
|
+
tenantId;
|
|
537
|
+
permissions;
|
|
538
|
+
constructor(id, name, permissions, tenantId) {
|
|
539
|
+
const normalizedName = name.trim();
|
|
540
|
+
if (normalizedName.length === 0) {
|
|
541
|
+
throw new Error("Role name cannot be empty");
|
|
542
|
+
}
|
|
543
|
+
this.id = id;
|
|
544
|
+
this.name = normalizedName;
|
|
545
|
+
this.permissions = Object.freeze([...permissions]);
|
|
546
|
+
this.tenantId = tenantId;
|
|
547
|
+
Object.freeze(this);
|
|
548
|
+
}
|
|
549
|
+
static create(input) {
|
|
550
|
+
return new _Role(
|
|
551
|
+
RoleId.create(input.id),
|
|
552
|
+
input.name,
|
|
553
|
+
(input.permissions ?? []).map((permission) => PermissionKey.create(permission)),
|
|
554
|
+
input.tenantId === void 0 ? void 0 : TenantId.create(input.tenantId)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
toPrimitives() {
|
|
558
|
+
const output = {
|
|
559
|
+
id: this.id.value,
|
|
560
|
+
name: this.name,
|
|
561
|
+
permissions: this.permissions.map((permission) => permission.value)
|
|
562
|
+
};
|
|
563
|
+
if (this.tenantId !== void 0) {
|
|
564
|
+
return { ...output, tenantId: this.tenantId.value };
|
|
565
|
+
}
|
|
566
|
+
return output;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/infrastructure/cache/memory-cache.ts
|
|
571
|
+
var MemoryCache = class {
|
|
572
|
+
entries = /* @__PURE__ */ new Map();
|
|
573
|
+
async get(key) {
|
|
574
|
+
const entry = this.entries.get(key);
|
|
575
|
+
if (entry === void 0) {
|
|
576
|
+
return void 0;
|
|
577
|
+
}
|
|
578
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
|
|
579
|
+
this.entries.delete(key);
|
|
580
|
+
return void 0;
|
|
581
|
+
}
|
|
582
|
+
return entry.value;
|
|
583
|
+
}
|
|
584
|
+
async set(key, value, ttlMs) {
|
|
585
|
+
const entry = ttlMs === void 0 ? { value } : { value, expiresAt: Date.now() + ttlMs };
|
|
586
|
+
this.entries.set(key, entry);
|
|
587
|
+
}
|
|
588
|
+
async delete(key) {
|
|
589
|
+
this.entries.delete(key);
|
|
590
|
+
}
|
|
591
|
+
async deleteByPrefix(prefix) {
|
|
592
|
+
for (const key of this.entries.keys()) {
|
|
593
|
+
if (key.startsWith(prefix)) {
|
|
594
|
+
this.entries.delete(key);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
clear() {
|
|
599
|
+
this.entries.clear();
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
603
|
+
0 && (module.exports = {
|
|
604
|
+
Assignment,
|
|
605
|
+
CheckPermissionUseCase,
|
|
606
|
+
CheckRoleUseCase,
|
|
607
|
+
DefaultRbacEngine,
|
|
608
|
+
MemoryCache,
|
|
609
|
+
Permission,
|
|
610
|
+
PermissionEvaluator,
|
|
611
|
+
PermissionKey,
|
|
612
|
+
PolicyKey,
|
|
613
|
+
PolicyRegistry,
|
|
614
|
+
RbacAuthorizationError,
|
|
615
|
+
RbacConfigurationError,
|
|
616
|
+
RbacError,
|
|
617
|
+
RbacValidationError,
|
|
618
|
+
ResourceId,
|
|
619
|
+
Role,
|
|
620
|
+
RoleId,
|
|
621
|
+
SystemClock,
|
|
622
|
+
TenantId,
|
|
623
|
+
UserId,
|
|
624
|
+
accessProfileCacheKey,
|
|
625
|
+
createRbacEngine,
|
|
626
|
+
matchesPermission,
|
|
627
|
+
normalizeContext
|
|
628
|
+
});
|
|
629
|
+
//# sourceMappingURL=index.cjs.map
|