@faryzal2020/v-perms 1.0.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.
@@ -0,0 +1,203 @@
1
+ import { generateWildcardPatterns } from '../utils/wildcard.js';
2
+
3
+ /**
4
+ * Core permission checking logic with caching and wildcard support
5
+ */
6
+ class PermissionChecker {
7
+ constructor(adapter, cacheManager, logger) {
8
+ this.adapter = adapter;
9
+ this.cache = cacheManager;
10
+ this.logger = logger;
11
+ }
12
+
13
+ /**
14
+ * Check if a user has a specific permission
15
+ * @param {string} userId
16
+ * @param {string} permissionKey
17
+ * @returns {Promise<boolean>}
18
+ */
19
+ async checkPermission(userId, permissionKey) {
20
+ this.logger.debug('checkPermission:', userId, permissionKey);
21
+
22
+ // Check cache first
23
+ const cached = await this.cache.get('user', userId, permissionKey);
24
+ if (cached !== null) {
25
+ this.logger.debug('Cache hit:', cached);
26
+ return cached;
27
+ }
28
+
29
+ const result = await this._checkPermissionUncached(userId, permissionKey);
30
+
31
+ // Cache result
32
+ await this.cache.set('user', result, userId, permissionKey);
33
+
34
+ return result;
35
+ }
36
+
37
+ /**
38
+ * Check if a role has a specific permission
39
+ * @param {string} roleId
40
+ * @param {string} permissionKey
41
+ * @returns {Promise<boolean>}
42
+ */
43
+ async checkRolePermission(roleId, permissionKey) {
44
+ this.logger.debug('checkRolePermission:', roleId, permissionKey);
45
+
46
+ // Check cache first
47
+ const cached = await this.cache.get('role', roleId, permissionKey);
48
+ if (cached !== null) {
49
+ return cached;
50
+ }
51
+
52
+ const result = await this._checkRolePermissionUncached(roleId, permissionKey);
53
+
54
+ // Cache result
55
+ await this.cache.set('role', result, roleId, permissionKey);
56
+
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * Check permission without using cache
62
+ * @private
63
+ */
64
+ async _checkPermissionUncached(userId, permissionKey) {
65
+ // 1. Check user-specific permissions (highest priority)
66
+ const userPerm = await this.adapter.getUserPermission(userId, permissionKey);
67
+ if (userPerm !== null) {
68
+ this.logger.debug('User direct permission:', userPerm.granted);
69
+ return userPerm.granted;
70
+ }
71
+
72
+ // Check user wildcards
73
+ const wildcardPatterns = generateWildcardPatterns(permissionKey);
74
+ for (const pattern of wildcardPatterns) {
75
+ const userWildcard = await this.adapter.getUserPermission(userId, pattern);
76
+ if (userWildcard !== null) {
77
+ this.logger.debug('User wildcard match:', pattern, userWildcard.granted);
78
+ return userWildcard.granted;
79
+ }
80
+ }
81
+
82
+ // 2. Get all roles with inheritance
83
+ const allRoles = await this._getUserRolesWithInheritance(userId);
84
+ this.logger.debug('User roles (with inheritance):', allRoles.map(r => r.name));
85
+
86
+ // 3. Check each role's permissions (by priority)
87
+ for (const role of allRoles) {
88
+ const rolePerm = await this.adapter.getRolePermission(role.id, permissionKey);
89
+ if (rolePerm !== null) {
90
+ this.logger.debug('Role direct permission:', role.name, rolePerm.granted);
91
+ return rolePerm.granted;
92
+ }
93
+
94
+ // Check role wildcards
95
+ for (const pattern of wildcardPatterns) {
96
+ const roleWildcard = await this.adapter.getRolePermission(role.id, pattern);
97
+ if (roleWildcard !== null) {
98
+ this.logger.debug('Role wildcard match:', role.name, pattern, roleWildcard.granted);
99
+ return roleWildcard.granted;
100
+ }
101
+ }
102
+ }
103
+
104
+ // 4. Default deny
105
+ this.logger.debug('No permission found, default deny');
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Check role permission without using cache
111
+ * @private
112
+ */
113
+ async _checkRolePermissionUncached(roleId, permissionKey) {
114
+ // Check direct permission
115
+ const rolePerm = await this.adapter.getRolePermission(roleId, permissionKey);
116
+ if (rolePerm !== null) {
117
+ return rolePerm.granted;
118
+ }
119
+
120
+ // Check wildcards
121
+ const wildcardPatterns = generateWildcardPatterns(permissionKey);
122
+ for (const pattern of wildcardPatterns) {
123
+ const roleWildcard = await this.adapter.getRolePermission(roleId, pattern);
124
+ if (roleWildcard !== null) {
125
+ return roleWildcard.granted;
126
+ }
127
+ }
128
+
129
+ // Check inherited roles
130
+ const inheritedRoles = await this._getRoleInheritanceRecursive(roleId);
131
+ for (const inheritedRole of inheritedRoles) {
132
+ const inheritedPerm = await this.adapter.getRolePermission(inheritedRole.id, permissionKey);
133
+ if (inheritedPerm !== null) {
134
+ return inheritedPerm.granted;
135
+ }
136
+
137
+ for (const pattern of wildcardPatterns) {
138
+ const inheritedWildcard = await this.adapter.getRolePermission(inheritedRole.id, pattern);
139
+ if (inheritedWildcard !== null) {
140
+ return inheritedWildcard.granted;
141
+ }
142
+ }
143
+ }
144
+
145
+ return false;
146
+ }
147
+
148
+ /**
149
+ * Get all roles for a user including inherited roles
150
+ * @private
151
+ */
152
+ async _getUserRolesWithInheritance(userId) {
153
+ const directRoles = await this.adapter.getUserRoles(userId);
154
+ const allRoles = new Map();
155
+
156
+ const collectRoles = async (roleId, visited = new Set()) => {
157
+ if (visited.has(roleId)) return;
158
+ visited.add(roleId);
159
+
160
+ const role = await this.adapter.getRole(roleId);
161
+ if (!role) return;
162
+
163
+ allRoles.set(role.id, role);
164
+
165
+ const inheritedRoles = await this.adapter.getRoleInheritance(roleId);
166
+ for (const inherited of inheritedRoles) {
167
+ await collectRoles(inherited.inheritsFromId, visited);
168
+ }
169
+ };
170
+
171
+ for (const role of directRoles) {
172
+ await collectRoles(role.id);
173
+ }
174
+
175
+ // Sort by priority (highest first)
176
+ return Array.from(allRoles.values()).sort((a, b) => b.priority - a.priority);
177
+ }
178
+
179
+ /**
180
+ * Get all inherited roles recursively
181
+ * @private
182
+ */
183
+ async _getRoleInheritanceRecursive(roleId, visited = new Set()) {
184
+ if (visited.has(roleId)) return [];
185
+ visited.add(roleId);
186
+
187
+ const inheritedRoles = await this.adapter.getRoleInheritance(roleId);
188
+ const allInherited = [];
189
+
190
+ for (const inherited of inheritedRoles) {
191
+ const role = await this.adapter.getRole(inherited.inheritsFromId);
192
+ if (role) {
193
+ allInherited.push(role);
194
+ const deeper = await this._getRoleInheritanceRecursive(inherited.inheritsFromId, visited);
195
+ allInherited.push(...deeper);
196
+ }
197
+ }
198
+
199
+ return allInherited;
200
+ }
201
+ }
202
+
203
+ export default PermissionChecker;
@@ -0,0 +1,410 @@
1
+ import {
2
+ RoleNotFoundError,
3
+ PermissionNotFoundError,
4
+ } from './errors.js';
5
+
6
+ /**
7
+ * High-level API for managing permissions, roles, and users
8
+ */
9
+ class PermissionManager {
10
+ constructor(adapter, checker, cacheManager, logger) {
11
+ this.adapter = adapter;
12
+ this.checker = checker;
13
+ this.cache = cacheManager;
14
+ this.logger = logger;
15
+ }
16
+
17
+ // ==================== Permission Operations ====================
18
+
19
+ /**
20
+ * Create a new permission
21
+ * @param {string} key - Permission key (e.g., 'endpoint.users.list')
22
+ * @param {string|null} description - Description of the permission
23
+ * @param {string|null} category - Category for grouping
24
+ * @returns {Promise<Object>}
25
+ */
26
+ async createPermission(key, description = null, category = null) {
27
+ this.logger.debug('createPermission:', key, description, category);
28
+ return await this.adapter.createPermission({ key, description, category });
29
+ }
30
+
31
+ /**
32
+ * Delete a permission
33
+ * @param {string} permissionKey
34
+ * @returns {Promise<boolean>}
35
+ */
36
+ async deletePermission(permissionKey) {
37
+ this.logger.debug('deletePermission:', permissionKey);
38
+ return await this.adapter.deletePermission(permissionKey);
39
+ }
40
+
41
+ /**
42
+ * List all permissions
43
+ * @returns {Promise<Array>}
44
+ */
45
+ async listPermissions() {
46
+ return await this.adapter.listAllPermissions();
47
+ }
48
+
49
+ /**
50
+ * Get a permission by key
51
+ * @param {string} permissionKey
52
+ * @returns {Promise<Object|null>}
53
+ */
54
+ async getPermission(permissionKey) {
55
+ return await this.adapter.getPermission(permissionKey);
56
+ }
57
+
58
+ // ==================== Role Operations ====================
59
+
60
+ /**
61
+ * Create a new role
62
+ * @param {string} name - Unique role name
63
+ * @param {string|null} description - Role description
64
+ * @param {number} priority - Role priority (higher = more important)
65
+ * @param {boolean} isDefault - Whether to auto-assign to new users
66
+ * @returns {Promise<Object>}
67
+ */
68
+ async createRole(name, description = null, priority = 0, isDefault = false) {
69
+ this.logger.debug('createRole:', name, description, priority, isDefault);
70
+ return await this.adapter.createRole({ name, description, priority, isDefault });
71
+ }
72
+
73
+ /**
74
+ * Delete a role
75
+ * @param {string} roleIdOrName - Role ID or name
76
+ * @returns {Promise<boolean>}
77
+ */
78
+ async deleteRole(roleIdOrName) {
79
+ this.logger.debug('deleteRole:', roleIdOrName);
80
+ const role = await this._resolveRole(roleIdOrName);
81
+ if (!role) {
82
+ throw new RoleNotFoundError(roleIdOrName);
83
+ }
84
+
85
+ // Invalidate cache for all users with this role
86
+ await this.cache.invalidateRole(role.id);
87
+
88
+ return await this.adapter.deleteRole(role.id);
89
+ }
90
+
91
+ /**
92
+ * List all roles
93
+ * @returns {Promise<Array>}
94
+ */
95
+ async listRoles() {
96
+ return await this.adapter.listAllRoles();
97
+ }
98
+
99
+ /**
100
+ * Get a role by ID or name
101
+ * @param {string} roleIdOrName
102
+ * @returns {Promise<Object|null>}
103
+ */
104
+ async getRole(roleIdOrName) {
105
+ return await this._resolveRole(roleIdOrName);
106
+ }
107
+
108
+ /**
109
+ * Update a role
110
+ * @param {string} roleIdOrName
111
+ * @param {Object} data - Fields to update
112
+ * @returns {Promise<Object>}
113
+ */
114
+ async updateRole(roleIdOrName, data) {
115
+ const role = await this._resolveRole(roleIdOrName);
116
+ if (!role) {
117
+ throw new RoleNotFoundError(roleIdOrName);
118
+ }
119
+
120
+ await this.cache.invalidateRole(role.id);
121
+ return await this.adapter.updateRole(role.id, data);
122
+ }
123
+
124
+ // ==================== Assignment Operations ====================
125
+
126
+ /**
127
+ * Assign permission to role or user
128
+ * @param {string} permissionKey - Permission key (supports wildcards)
129
+ * @param {string} targetId - Role ID/name or User ID
130
+ * @param {string} targetType - 'role' or 'user'
131
+ * @returns {Promise<Object>}
132
+ */
133
+ async assignPermission(permissionKey, targetId, targetType = 'role') {
134
+ this.logger.debug('assignPermission:', permissionKey, targetId, targetType);
135
+
136
+ if (targetType === 'role') {
137
+ const role = await this._resolveRole(targetId);
138
+ if (!role) {
139
+ throw new RoleNotFoundError(targetId);
140
+ }
141
+
142
+ await this.cache.invalidateRole(role.id);
143
+ return await this.adapter.assignPermissionToRole(permissionKey, role.id, true);
144
+ } else if (targetType === 'user') {
145
+ await this.cache.invalidateUser(targetId);
146
+ return await this.adapter.assignPermissionToUser(permissionKey, targetId, true);
147
+ } else {
148
+ throw new Error(`Invalid targetType: ${targetType}. Must be 'role' or 'user'.`);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Ban/deny permission (set granted=false)
154
+ * @param {string} permissionKey
155
+ * @param {string} targetId - Role ID/name or User ID
156
+ * @param {string} targetType - 'role' or 'user'
157
+ * @returns {Promise<Object>}
158
+ */
159
+ async banPermission(permissionKey, targetId, targetType = 'role') {
160
+ this.logger.debug('banPermission:', permissionKey, targetId, targetType);
161
+
162
+ if (targetType === 'role') {
163
+ const role = await this._resolveRole(targetId);
164
+ if (!role) {
165
+ throw new RoleNotFoundError(targetId);
166
+ }
167
+
168
+ await this.cache.invalidateRole(role.id);
169
+ return await this.adapter.assignPermissionToRole(permissionKey, role.id, false);
170
+ } else if (targetType === 'user') {
171
+ await this.cache.invalidateUser(targetId);
172
+ return await this.adapter.assignPermissionToUser(permissionKey, targetId, false);
173
+ } else {
174
+ throw new Error(`Invalid targetType: ${targetType}. Must be 'role' or 'user'.`);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Remove permission assignment
180
+ * @param {string} permissionKey
181
+ * @param {string} targetId - Role ID/name or User ID
182
+ * @param {string} targetType - 'role' or 'user'
183
+ * @returns {Promise<boolean>}
184
+ */
185
+ async removePermission(permissionKey, targetId, targetType = 'role') {
186
+ this.logger.debug('removePermission:', permissionKey, targetId, targetType);
187
+
188
+ if (targetType === 'role') {
189
+ const role = await this._resolveRole(targetId);
190
+ if (!role) {
191
+ throw new RoleNotFoundError(targetId);
192
+ }
193
+
194
+ await this.cache.invalidateRole(role.id);
195
+ return await this.adapter.removePermissionFromRole(permissionKey, role.id);
196
+ } else if (targetType === 'user') {
197
+ await this.cache.invalidateUser(targetId);
198
+ return await this.adapter.removePermissionFromUser(permissionKey, targetId);
199
+ } else {
200
+ throw new Error(`Invalid targetType: ${targetType}. Must be 'role' or 'user'.`);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Assign role to user
206
+ * @param {string} roleIdOrName
207
+ * @param {string} userId
208
+ * @returns {Promise<Object>}
209
+ */
210
+ async assignRole(roleIdOrName, userId) {
211
+ this.logger.debug('assignRole:', roleIdOrName, userId);
212
+
213
+ const role = await this._resolveRole(roleIdOrName);
214
+ if (!role) {
215
+ throw new RoleNotFoundError(roleIdOrName);
216
+ }
217
+
218
+ await this.cache.invalidateUser(userId);
219
+ return await this.adapter.assignRoleToUser(userId, role.id);
220
+ }
221
+
222
+ /**
223
+ * Remove role from user
224
+ * @param {string} roleIdOrName
225
+ * @param {string} userId
226
+ * @returns {Promise<boolean>}
227
+ */
228
+ async removeRole(roleIdOrName, userId) {
229
+ this.logger.debug('removeRole:', roleIdOrName, userId);
230
+
231
+ const role = await this._resolveRole(roleIdOrName);
232
+ if (!role) {
233
+ throw new RoleNotFoundError(roleIdOrName);
234
+ }
235
+
236
+ await this.cache.invalidateUser(userId);
237
+ return await this.adapter.removeRoleFromUser(userId, role.id);
238
+ }
239
+
240
+ /**
241
+ * Set role inheritance (roleId inherits from inheritFromRoleId)
242
+ * @param {string} roleIdOrName
243
+ * @param {string} inheritFromRoleIdOrName
244
+ * @param {number} priority
245
+ * @returns {Promise<Object>}
246
+ */
247
+ async setRoleInheritance(roleIdOrName, inheritFromRoleIdOrName, priority = 0) {
248
+ this.logger.debug('setRoleInheritance:', roleIdOrName, inheritFromRoleIdOrName, priority);
249
+
250
+ const role = await this._resolveRole(roleIdOrName);
251
+ if (!role) {
252
+ throw new RoleNotFoundError(roleIdOrName);
253
+ }
254
+
255
+ const inheritFromRole = await this._resolveRole(inheritFromRoleIdOrName);
256
+ if (!inheritFromRole) {
257
+ throw new RoleNotFoundError(inheritFromRoleIdOrName);
258
+ }
259
+
260
+ await this.cache.invalidateRole(role.id);
261
+ return await this.adapter.setRoleInheritance(role.id, inheritFromRole.id, priority);
262
+ }
263
+
264
+ /**
265
+ * Remove role inheritance
266
+ * @param {string} roleIdOrName
267
+ * @param {string} inheritFromRoleIdOrName
268
+ * @returns {Promise<boolean>}
269
+ */
270
+ async removeRoleInheritance(roleIdOrName, inheritFromRoleIdOrName) {
271
+ this.logger.debug('removeRoleInheritance:', roleIdOrName, inheritFromRoleIdOrName);
272
+
273
+ const role = await this._resolveRole(roleIdOrName);
274
+ if (!role) {
275
+ throw new RoleNotFoundError(roleIdOrName);
276
+ }
277
+
278
+ const inheritFromRole = await this._resolveRole(inheritFromRoleIdOrName);
279
+ if (!inheritFromRole) {
280
+ throw new RoleNotFoundError(inheritFromRoleIdOrName);
281
+ }
282
+
283
+ await this.cache.invalidateRole(role.id);
284
+ return await this.adapter.removeRoleInheritance(role.id, inheritFromRole.id);
285
+ }
286
+
287
+ // ==================== Check Operations ====================
288
+
289
+ /**
290
+ * Check if user/role has permission
291
+ * @param {string} targetId - User ID or Role ID
292
+ * @param {string} permissionKey
293
+ * @param {string} targetType - 'user' or 'role'
294
+ * @returns {Promise<boolean>}
295
+ */
296
+ async checkPermission(targetId, permissionKey, targetType = 'user') {
297
+ if (targetType === 'user') {
298
+ return await this.checker.checkPermission(targetId, permissionKey);
299
+ } else if (targetType === 'role') {
300
+ return await this.checker.checkRolePermission(targetId, permissionKey);
301
+ } else {
302
+ throw new Error(`Invalid targetType: ${targetType}. Must be 'user' or 'role'.`);
303
+ }
304
+ }
305
+
306
+ // ==================== Query Operations ====================
307
+
308
+ /**
309
+ * Get all roles assigned to a user
310
+ * @param {string} userId
311
+ * @returns {Promise<Array>}
312
+ */
313
+ async getUserRoles(userId) {
314
+ return await this.adapter.getUserRoles(userId);
315
+ }
316
+
317
+ /**
318
+ * Get all permissions for a user (direct + role-based)
319
+ * @param {string} userId
320
+ * @returns {Promise<Object>} - { direct: [], fromRoles: [] }
321
+ */
322
+ async getUserPermissions(userId) {
323
+ const directPermissions = await this.adapter.getUserDirectPermissions(userId);
324
+ const roles = await this.adapter.getUserRoles(userId);
325
+
326
+ const rolePermissions = [];
327
+ for (const role of roles) {
328
+ const permissions = await this.adapter.getRolePermissions(role.id);
329
+ rolePermissions.push({
330
+ role: role.name,
331
+ roleId: role.id,
332
+ permissions,
333
+ });
334
+ }
335
+
336
+ return {
337
+ direct: directPermissions,
338
+ fromRoles: rolePermissions,
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Get all permissions assigned to a role
344
+ * @param {string} roleIdOrName
345
+ * @returns {Promise<Array>}
346
+ */
347
+ async getRolePermissions(roleIdOrName) {
348
+ const role = await this._resolveRole(roleIdOrName);
349
+ if (!role) {
350
+ throw new RoleNotFoundError(roleIdOrName);
351
+ }
352
+ return await this.adapter.getRolePermissions(role.id);
353
+ }
354
+
355
+ /**
356
+ * Get roles that a role inherits from
357
+ * @param {string} roleIdOrName
358
+ * @returns {Promise<Array>}
359
+ */
360
+ async getRoleInheritance(roleIdOrName) {
361
+ const role = await this._resolveRole(roleIdOrName);
362
+ if (!role) {
363
+ throw new RoleNotFoundError(roleIdOrName);
364
+ }
365
+ return await this.adapter.getRoleInheritance(role.id);
366
+ }
367
+
368
+ // ==================== Cache Operations ====================
369
+
370
+ /**
371
+ * Invalidate cache for a specific user
372
+ * @param {string} userId
373
+ */
374
+ async invalidateUserCache(userId) {
375
+ return await this.cache.invalidateUser(userId);
376
+ }
377
+
378
+ /**
379
+ * Invalidate cache for a specific role
380
+ * @param {string} roleId
381
+ */
382
+ async invalidateRoleCache(roleId) {
383
+ return await this.cache.invalidateRole(roleId);
384
+ }
385
+
386
+ /**
387
+ * Clear entire cache
388
+ */
389
+ async clearAllCache() {
390
+ return await this.cache.clear();
391
+ }
392
+
393
+ // ==================== Helper Methods ====================
394
+
395
+ /**
396
+ * Resolve role by ID or name
397
+ * @private
398
+ */
399
+ async _resolveRole(roleIdOrName) {
400
+ // First try as ID
401
+ let role = await this.adapter.getRole(roleIdOrName);
402
+ if (role) return role;
403
+
404
+ // Then try as name
405
+ role = await this.adapter.getRoleByName(roleIdOrName);
406
+ return role;
407
+ }
408
+ }
409
+
410
+ export default PermissionManager;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Base error class for permission-related errors
3
+ */
4
+ class PermissionError extends Error {
5
+ constructor(message, code, details = {}) {
6
+ super(message);
7
+ this.name = 'PermissionError';
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Error thrown when a user is not found
15
+ */
16
+ class UserNotFoundError extends PermissionError {
17
+ constructor(userId) {
18
+ super(`User not found: ${userId}`, 'USER_NOT_FOUND', { userId });
19
+ this.name = 'UserNotFoundError';
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Error thrown when a role is not found
25
+ */
26
+ class RoleNotFoundError extends PermissionError {
27
+ constructor(roleId) {
28
+ super(`Role not found: ${roleId}`, 'ROLE_NOT_FOUND', { roleId });
29
+ this.name = 'RoleNotFoundError';
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Error thrown when a permission is not found
35
+ */
36
+ class PermissionNotFoundError extends PermissionError {
37
+ constructor(permissionKey) {
38
+ super(`Permission not found: ${permissionKey}`, 'PERMISSION_NOT_FOUND', { permissionKey });
39
+ this.name = 'PermissionNotFoundError';
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Error thrown when a role is already assigned to a user
45
+ */
46
+ class RoleAlreadyAssignedError extends PermissionError {
47
+ constructor(userId, roleId) {
48
+ super(`Role already assigned to user`, 'ROLE_ALREADY_ASSIGNED', { userId, roleId });
49
+ this.name = 'RoleAlreadyAssignedError';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Error thrown when a permission already exists
55
+ */
56
+ class PermissionAlreadyExistsError extends PermissionError {
57
+ constructor(permissionKey) {
58
+ super(`Permission already exists: ${permissionKey}`, 'PERMISSION_EXISTS', { permissionKey });
59
+ this.name = 'PermissionAlreadyExistsError';
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Error thrown when a role already exists
65
+ */
66
+ class RoleAlreadyExistsError extends PermissionError {
67
+ constructor(roleName) {
68
+ super(`Role already exists: ${roleName}`, 'ROLE_EXISTS', { roleName });
69
+ this.name = 'RoleAlreadyExistsError';
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Error thrown when circular inheritance is detected
75
+ */
76
+ class CircularInheritanceError extends PermissionError {
77
+ constructor(roleId, inheritsFromId) {
78
+ super(`Circular inheritance detected`, 'CIRCULAR_INHERITANCE', { roleId, inheritsFromId });
79
+ this.name = 'CircularInheritanceError';
80
+ }
81
+ }
82
+
83
+ export {
84
+ PermissionError,
85
+ UserNotFoundError,
86
+ RoleNotFoundError,
87
+ PermissionNotFoundError,
88
+ RoleAlreadyAssignedError,
89
+ PermissionAlreadyExistsError,
90
+ RoleAlreadyExistsError,
91
+ CircularInheritanceError,
92
+ };