@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.
- package/LICENSE +21 -0
- package/README.md +527 -0
- package/package.json +41 -0
- package/src/adapters/BaseAdapter.js +262 -0
- package/src/adapters/PrismaAdapter.js +476 -0
- package/src/adapters/index.js +5 -0
- package/src/core/CacheManager.js +151 -0
- package/src/core/PermissionChecker.js +203 -0
- package/src/core/PermissionManager.js +410 -0
- package/src/core/errors.js +92 -0
- package/src/index.js +73 -0
- package/src/prisma/schema.prisma +86 -0
- package/src/utils/logger.js +64 -0
- package/src/utils/wildcard.js +44 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import PrismaAdapter from './adapters/PrismaAdapter.js';
|
|
2
|
+
import PermissionChecker from './core/PermissionChecker.js';
|
|
3
|
+
import PermissionManager from './core/PermissionManager.js';
|
|
4
|
+
import CacheManager from './core/CacheManager.js';
|
|
5
|
+
import Logger from './utils/logger.js';
|
|
6
|
+
import * as errors from './core/errors.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create permission system instance
|
|
10
|
+
* @param {PrismaClient} prismaClient - Prisma client instance
|
|
11
|
+
* @param {object} options - Configuration options
|
|
12
|
+
* @param {object} options.redis - Redis client instance (optional)
|
|
13
|
+
* @param {boolean} options.enableCache - Enable caching (default: true)
|
|
14
|
+
* @param {number} options.cacheTTL - Cache TTL in seconds (default: 300)
|
|
15
|
+
* @param {boolean} options.debug - Enable debug logging (default: false)
|
|
16
|
+
* @returns {Object} Permission system instance
|
|
17
|
+
*/
|
|
18
|
+
function createPermissionSystem(prismaClient, options = {}) {
|
|
19
|
+
const {
|
|
20
|
+
redis = null,
|
|
21
|
+
enableCache = true,
|
|
22
|
+
cacheTTL = 300,
|
|
23
|
+
debug = false,
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
const logger = new Logger(debug);
|
|
27
|
+
const cacheManager = new CacheManager(redis, { enabled: enableCache, ttl: cacheTTL });
|
|
28
|
+
const adapter = new PrismaAdapter(prismaClient, logger);
|
|
29
|
+
const checker = new PermissionChecker(adapter, cacheManager, logger);
|
|
30
|
+
const manager = new PermissionManager(adapter, checker, cacheManager, logger);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
// Core components
|
|
34
|
+
manager,
|
|
35
|
+
checker,
|
|
36
|
+
adapter,
|
|
37
|
+
cache: cacheManager,
|
|
38
|
+
logger,
|
|
39
|
+
|
|
40
|
+
// Convenience methods for permission checking
|
|
41
|
+
can: (userId, permission) => checker.checkPermission(userId, permission),
|
|
42
|
+
canRole: (roleId, permission) => checker.checkRolePermission(roleId, permission),
|
|
43
|
+
|
|
44
|
+
// Direct access to commonly used manager methods
|
|
45
|
+
createPermission: (...args) => manager.createPermission(...args),
|
|
46
|
+
deletePermission: (...args) => manager.deletePermission(...args),
|
|
47
|
+
createRole: (...args) => manager.createRole(...args),
|
|
48
|
+
deleteRole: (...args) => manager.deleteRole(...args),
|
|
49
|
+
assignPermission: (...args) => manager.assignPermission(...args),
|
|
50
|
+
removePermission: (...args) => manager.removePermission(...args),
|
|
51
|
+
assignRole: (...args) => manager.assignRole(...args),
|
|
52
|
+
removeRole: (...args) => manager.removeRole(...args),
|
|
53
|
+
banPermission: (...args) => manager.banPermission(...args),
|
|
54
|
+
checkPermission: (...args) => manager.checkPermission(...args),
|
|
55
|
+
|
|
56
|
+
// Cache control
|
|
57
|
+
invalidateUserCache: (userId) => cacheManager.invalidateUser(userId),
|
|
58
|
+
invalidateRoleCache: (roleId) => cacheManager.invalidateRole(roleId),
|
|
59
|
+
clearCache: () => cacheManager.clear(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
createPermissionSystem,
|
|
65
|
+
PrismaAdapter,
|
|
66
|
+
PermissionChecker,
|
|
67
|
+
PermissionManager,
|
|
68
|
+
CacheManager,
|
|
69
|
+
Logger,
|
|
70
|
+
errors,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default createPermissionSystem;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// This schema is designed to be copied into your existing Prisma schema
|
|
2
|
+
// Compatible with PostgreSQL, MySQL, and SQLite
|
|
3
|
+
|
|
4
|
+
model Role {
|
|
5
|
+
id String @id @default(cuid())
|
|
6
|
+
name String @unique
|
|
7
|
+
description String?
|
|
8
|
+
priority Int @default(0)
|
|
9
|
+
isDefault Boolean @default(false)
|
|
10
|
+
createdAt DateTime @default(now())
|
|
11
|
+
updatedAt DateTime @updatedAt
|
|
12
|
+
|
|
13
|
+
userRoles UserRole[]
|
|
14
|
+
rolePermissions RolePermission[]
|
|
15
|
+
inheritedRoles RoleInheritance[] @relation("ParentRole")
|
|
16
|
+
inheritsFrom RoleInheritance[] @relation("ChildRole")
|
|
17
|
+
|
|
18
|
+
@@map("roles")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
model Permission {
|
|
22
|
+
id String @id @default(cuid())
|
|
23
|
+
key String @unique
|
|
24
|
+
description String?
|
|
25
|
+
category String?
|
|
26
|
+
createdAt DateTime @default(now())
|
|
27
|
+
|
|
28
|
+
rolePermissions RolePermission[]
|
|
29
|
+
userPermissions UserPermission[]
|
|
30
|
+
|
|
31
|
+
@@index([category])
|
|
32
|
+
@@map("permissions")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
model UserRole {
|
|
36
|
+
userId String
|
|
37
|
+
roleId String
|
|
38
|
+
assignedAt DateTime @default(now())
|
|
39
|
+
|
|
40
|
+
// Note: Replace 'User' with your actual User model name
|
|
41
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
42
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
43
|
+
|
|
44
|
+
@@id([userId, roleId])
|
|
45
|
+
@@map("user_roles")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
model RolePermission {
|
|
49
|
+
roleId String
|
|
50
|
+
permissionId String
|
|
51
|
+
granted Boolean @default(true)
|
|
52
|
+
assignedAt DateTime @default(now())
|
|
53
|
+
|
|
54
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
55
|
+
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
|
56
|
+
|
|
57
|
+
@@id([roleId, permissionId])
|
|
58
|
+
@@map("role_permissions")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
model UserPermission {
|
|
62
|
+
userId String
|
|
63
|
+
permissionId String
|
|
64
|
+
granted Boolean @default(true)
|
|
65
|
+
assignedAt DateTime @default(now())
|
|
66
|
+
|
|
67
|
+
// Note: Replace 'User' with your actual User model name
|
|
68
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
69
|
+
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
|
70
|
+
|
|
71
|
+
@@id([userId, permissionId])
|
|
72
|
+
@@map("user_permissions")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
model RoleInheritance {
|
|
76
|
+
roleId String
|
|
77
|
+
inheritsFromId String
|
|
78
|
+
priority Int @default(0)
|
|
79
|
+
createdAt DateTime @default(now())
|
|
80
|
+
|
|
81
|
+
role Role @relation("ParentRole", fields: [roleId], references: [id], onDelete: Cascade)
|
|
82
|
+
inheritsFrom Role @relation("ChildRole", fields: [inheritsFromId], references: [id], onDelete: Cascade)
|
|
83
|
+
|
|
84
|
+
@@id([roleId, inheritsFromId])
|
|
85
|
+
@@map("role_inheritance")
|
|
86
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logging utility with on/off toggle
|
|
3
|
+
*/
|
|
4
|
+
class Logger {
|
|
5
|
+
constructor(enabled = false) {
|
|
6
|
+
this.enabled = enabled;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enable debug logging
|
|
11
|
+
*/
|
|
12
|
+
enable() {
|
|
13
|
+
this.enabled = true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Disable debug logging
|
|
18
|
+
*/
|
|
19
|
+
disable() {
|
|
20
|
+
this.enabled = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Log debug message
|
|
25
|
+
* @param {...any} args - Arguments to log
|
|
26
|
+
*/
|
|
27
|
+
debug(...args) {
|
|
28
|
+
if (this.enabled) {
|
|
29
|
+
console.log('[v-perms:debug]', ...args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Log info message
|
|
35
|
+
* @param {...any} args - Arguments to log
|
|
36
|
+
*/
|
|
37
|
+
info(...args) {
|
|
38
|
+
if (this.enabled) {
|
|
39
|
+
console.info('[v-perms:info]', ...args);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Log warning message
|
|
45
|
+
* @param {...any} args - Arguments to log
|
|
46
|
+
*/
|
|
47
|
+
warn(...args) {
|
|
48
|
+
if (this.enabled) {
|
|
49
|
+
console.warn('[v-perms:warn]', ...args);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log error message
|
|
55
|
+
* @param {...any} args - Arguments to log
|
|
56
|
+
*/
|
|
57
|
+
error(...args) {
|
|
58
|
+
if (this.enabled) {
|
|
59
|
+
console.error('[v-perms:error]', ...args);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Logger;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a permission key matches a wildcard pattern
|
|
3
|
+
* @param {string} pattern - Pattern like "endpoint.*" or "*"
|
|
4
|
+
* @param {string} permissionKey - Actual permission like "endpoint.admin.users"
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
function matchesWildcard(pattern, permissionKey) {
|
|
8
|
+
// Universal wildcard matches everything
|
|
9
|
+
if (pattern === '*') return true;
|
|
10
|
+
|
|
11
|
+
// Exact match
|
|
12
|
+
if (pattern === permissionKey) return true;
|
|
13
|
+
|
|
14
|
+
// Wildcard suffix match (e.g., "endpoint.*")
|
|
15
|
+
if (pattern.endsWith('.*')) {
|
|
16
|
+
const prefix = pattern.slice(0, -2);
|
|
17
|
+
// Matches the prefix itself or anything starting with prefix.
|
|
18
|
+
return permissionKey === prefix || permissionKey.startsWith(prefix + '.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate all possible wildcard patterns for a permission key
|
|
26
|
+
* @param {string} permissionKey - Like "endpoint.admin.users.delete"
|
|
27
|
+
* @returns {string[]} - ["endpoint.admin.users.*", "endpoint.admin.*", "endpoint.*", "*"]
|
|
28
|
+
*/
|
|
29
|
+
function generateWildcardPatterns(permissionKey) {
|
|
30
|
+
const parts = permissionKey.split('.');
|
|
31
|
+
const patterns = ['*'];
|
|
32
|
+
|
|
33
|
+
// Generate patterns from most specific to least specific
|
|
34
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
35
|
+
patterns.push([...parts.slice(0, i), '*'].join('.'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return patterns;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
matchesWildcard,
|
|
43
|
+
generateWildcardPatterns,
|
|
44
|
+
};
|