@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 faryzal2020
|
|
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
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
# @faryzal2020/v-perms
|
|
2
|
+
|
|
3
|
+
Minimal, flexible role-based permission system for JavaScript/Bun.js applications with Prisma.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Role-based access control (RBAC)** - Assign roles to users, permissions to roles
|
|
8
|
+
- **Role inheritance** - Roles can inherit permissions from other roles
|
|
9
|
+
- **User-specific overrides** - Direct user permissions take priority over role permissions
|
|
10
|
+
- **Wildcard permissions** - Grant access to multiple permissions at once (`endpoint.*`, `*`)
|
|
11
|
+
- **Ban/deny permissions** - Explicitly deny access even when wildcards would allow
|
|
12
|
+
- **Redis caching** - Optional distributed caching for high performance
|
|
13
|
+
- **Database agnostic** - Compatible with PostgreSQL, MySQL, SQLite via Prisma
|
|
14
|
+
- **Runtime flexible** - Works with both Bun.js and Node.js
|
|
15
|
+
- **Zero dependencies** - Only requires Prisma (peer dependency)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @faryzal2020/v-perms
|
|
21
|
+
# or
|
|
22
|
+
bun add @faryzal2020/v-perms
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Add Schema to Your Prisma Schema
|
|
28
|
+
|
|
29
|
+
Copy the models from `src/prisma/schema.prisma` into your existing Prisma schema file. See [examples/schema-usage.md](examples/schema-usage.md) for detailed instructions.
|
|
30
|
+
|
|
31
|
+
### 2. Run Migrations
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx prisma migrate dev --name add-permissions
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Initialize in Your Code
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { PrismaClient } from '@prisma/client';
|
|
41
|
+
import { createPermissionSystem } from '@faryzal2020/v-perms';
|
|
42
|
+
|
|
43
|
+
const prisma = new PrismaClient();
|
|
44
|
+
const perms = createPermissionSystem(prisma, {
|
|
45
|
+
debug: true, // Enable debug logging
|
|
46
|
+
enableCache: true,
|
|
47
|
+
cacheTTL: 300, // 5 minutes
|
|
48
|
+
redis: redisClient, // optional
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 4. Create Permissions and Roles
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// Create permissions
|
|
56
|
+
await perms.createPermission('page.admin', 'Access admin pages', 'page');
|
|
57
|
+
await perms.createPermission('endpoint.users.list', 'List users', 'endpoint');
|
|
58
|
+
await perms.createPermission('endpoint.users.delete', 'Delete users', 'endpoint');
|
|
59
|
+
|
|
60
|
+
// Create roles
|
|
61
|
+
await perms.createRole('member', 'Basic user role', 1, true); // isDefault=true
|
|
62
|
+
await perms.createRole('admin', 'Administrator', 10);
|
|
63
|
+
|
|
64
|
+
// Assign permissions to roles
|
|
65
|
+
await perms.assignPermission('page.admin', 'admin', 'role');
|
|
66
|
+
await perms.assignPermission('endpoint.*', 'admin', 'role'); // Wildcard
|
|
67
|
+
|
|
68
|
+
// Set role inheritance (admin inherits member permissions)
|
|
69
|
+
const memberRole = await perms.manager.getRole('member');
|
|
70
|
+
const adminRole = await perms.manager.getRole('admin');
|
|
71
|
+
await perms.manager.setRoleInheritance(adminRole.id, memberRole.id);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 5. Assign Roles to Users
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const adminRole = await perms.manager.getRole('admin');
|
|
78
|
+
await perms.assignRole(adminRole.id, userId);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 6. Check Permissions
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// Check user permission
|
|
85
|
+
const canAccess = await perms.can(userId, 'page.admin');
|
|
86
|
+
|
|
87
|
+
// Check role permission
|
|
88
|
+
const roleCanAccess = await perms.canRole(roleId, 'endpoint.users.delete');
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API Reference
|
|
92
|
+
|
|
93
|
+
### Factory Function
|
|
94
|
+
|
|
95
|
+
#### `createPermissionSystem(prismaClient, options)`
|
|
96
|
+
|
|
97
|
+
Creates a permission system instance.
|
|
98
|
+
|
|
99
|
+
**Parameters:**
|
|
100
|
+
- `prismaClient` (PrismaClient): Prisma client instance
|
|
101
|
+
- `options` (object):
|
|
102
|
+
- `redis` (RedisClient): Redis client for caching (optional)
|
|
103
|
+
- `enableCache` (boolean): Enable caching (default: `true`)
|
|
104
|
+
- `cacheTTL` (number): Cache TTL in seconds (default: `300`)
|
|
105
|
+
- `debug` (boolean): Enable debug logging (default: `false`)
|
|
106
|
+
|
|
107
|
+
**Returns:** Permission system instance with methods
|
|
108
|
+
|
|
109
|
+
### Permission Operations
|
|
110
|
+
|
|
111
|
+
#### `createPermission(key, description?, category?)`
|
|
112
|
+
|
|
113
|
+
Create a new permission.
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
await perms.createPermission('endpoint.posts.create', 'Create posts', 'endpoint');
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `deletePermission(permissionKey)`
|
|
120
|
+
|
|
121
|
+
Delete a permission.
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
await perms.deletePermission('endpoint.posts.create');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### `manager.listPermissions()`
|
|
128
|
+
|
|
129
|
+
List all permissions.
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
const permissions = await perms.manager.listPermissions();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Role Operations
|
|
136
|
+
|
|
137
|
+
#### `createRole(name, description?, priority?, isDefault?)`
|
|
138
|
+
|
|
139
|
+
Create a new role.
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
await perms.createRole('moderator', 'Moderator role', 5, false);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Parameters:**
|
|
146
|
+
- `name` (string): Unique role name
|
|
147
|
+
- `description` (string): Role description
|
|
148
|
+
- `priority` (number): Role priority (higher = more important, default: `0`)
|
|
149
|
+
- `isDefault` (boolean): Auto-assign to new users (default: `false`)
|
|
150
|
+
|
|
151
|
+
#### `deleteRole(roleIdOrName)`
|
|
152
|
+
|
|
153
|
+
Delete a role.
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
await perms.manager.deleteRole('moderator');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### `manager.getRole(roleIdOrName)`
|
|
160
|
+
|
|
161
|
+
Get role by ID or name.
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const role = await perms.manager.getRole('admin');
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `manager.listRoles()`
|
|
168
|
+
|
|
169
|
+
List all roles.
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const roles = await perms.manager.listRoles();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Assignment Operations
|
|
176
|
+
|
|
177
|
+
#### `assignPermission(permissionKey, targetId, targetType)`
|
|
178
|
+
|
|
179
|
+
Assign permission to role or user.
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
// Assign to role
|
|
183
|
+
await perms.assignPermission('page.admin', roleId, 'role');
|
|
184
|
+
|
|
185
|
+
// Assign to user (user-specific override)
|
|
186
|
+
await perms.assignPermission('feature.export', userId, 'user');
|
|
187
|
+
|
|
188
|
+
// Wildcard assignment
|
|
189
|
+
await perms.assignPermission('endpoint.*', roleId, 'role');
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `banPermission(permissionKey, targetId, targetType)`
|
|
193
|
+
|
|
194
|
+
Explicitly deny a permission (sets `granted=false`).
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// Ban admin page access for specific user
|
|
198
|
+
await perms.banPermission('page.admin', userId, 'user');
|
|
199
|
+
|
|
200
|
+
// Admin has endpoint.* but ban delete specifically
|
|
201
|
+
await perms.assignPermission('endpoint.*', adminRoleId, 'role');
|
|
202
|
+
await perms.banPermission('endpoint.users.delete', adminRoleId, 'role');
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### `assignRole(roleIdOrName, userId)`
|
|
206
|
+
|
|
207
|
+
Assign role to user.
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
await perms.assignRole('admin', userId);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### `manager.removeRole(roleIdOrName, userId)`
|
|
214
|
+
|
|
215
|
+
Remove role from user.
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
await perms.manager.removeRole('admin', userId);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `manager.setRoleInheritance(roleId, inheritFromRoleId, priority?)`
|
|
222
|
+
|
|
223
|
+
Set role inheritance.
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
// Admin inherits all member permissions
|
|
227
|
+
await perms.manager.setRoleInheritance(adminRole.id, memberRole.id, 1);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Check Operations
|
|
231
|
+
|
|
232
|
+
#### `can(userId, permissionKey)`
|
|
233
|
+
|
|
234
|
+
Check if user has permission. This is the main method you'll use.
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const hasAccess = await perms.can(userId, 'endpoint.users.delete');
|
|
238
|
+
|
|
239
|
+
if (hasAccess) {
|
|
240
|
+
// Allow action
|
|
241
|
+
} else {
|
|
242
|
+
// Deny action
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Resolution Order:**
|
|
247
|
+
1. User-specific permissions (highest priority)
|
|
248
|
+
2. User-specific wildcard permissions
|
|
249
|
+
3. Direct role permissions (by role priority)
|
|
250
|
+
4. Role wildcard permissions
|
|
251
|
+
5. Inherited role permissions
|
|
252
|
+
6. Inherited role wildcard permissions
|
|
253
|
+
7. Default deny
|
|
254
|
+
|
|
255
|
+
#### `canRole(roleId, permissionKey)`
|
|
256
|
+
|
|
257
|
+
Check if role has permission.
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const roleHasAccess = await perms.canRole(roleId, 'page.admin');
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Cache Operations
|
|
264
|
+
|
|
265
|
+
#### `invalidateUserCache(userId)`
|
|
266
|
+
|
|
267
|
+
Clear cache for specific user.
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
await perms.invalidateUserCache(userId);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### `invalidateRoleCache(roleId)`
|
|
274
|
+
|
|
275
|
+
Clear cache for specific role.
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
await perms.invalidateRoleCache(roleId);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `clearCache()`
|
|
282
|
+
|
|
283
|
+
Clear entire permission cache.
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
await perms.clearCache();
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Wildcard Permissions
|
|
290
|
+
|
|
291
|
+
Wildcard permissions allow you to grant access to multiple permissions at once.
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// Grant access to all endpoint permissions
|
|
295
|
+
await perms.assignPermission('endpoint.*', roleId, 'role');
|
|
296
|
+
|
|
297
|
+
// Now user has access to:
|
|
298
|
+
// - endpoint.users.list
|
|
299
|
+
// - endpoint.users.create
|
|
300
|
+
// - endpoint.posts.delete
|
|
301
|
+
// etc.
|
|
302
|
+
|
|
303
|
+
// Grant all permissions (god mode)
|
|
304
|
+
await perms.assignPermission('*', superadminRoleId, 'role');
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Wildcard Matching Rules
|
|
308
|
+
|
|
309
|
+
- `*` matches everything
|
|
310
|
+
- `endpoint.*` matches `endpoint.users`, `endpoint.posts.create`, etc.
|
|
311
|
+
- `endpoint.users.*` matches `endpoint.users.list`, `endpoint.users.delete`, etc.
|
|
312
|
+
- Wildcards don't match partial segments: `endpoint.*` does NOT match `endpoint` itself
|
|
313
|
+
|
|
314
|
+
## Ban/Deny Permissions
|
|
315
|
+
|
|
316
|
+
You can explicitly deny permissions, which is useful for exceptions.
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
// Admin has all endpoint access
|
|
320
|
+
await perms.assignPermission('endpoint.*', adminRoleId, 'role');
|
|
321
|
+
|
|
322
|
+
// But ban delete operations
|
|
323
|
+
await perms.banPermission('endpoint.users.delete', adminRoleId, 'role');
|
|
324
|
+
await perms.banPermission('endpoint.posts.delete', adminRoleId, 'role');
|
|
325
|
+
|
|
326
|
+
// User-specific ban (override role permissions)
|
|
327
|
+
await perms.banPermission('endpoint.sensitive', userId, 'user');
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Priority:** Deny (`granted=false`) takes precedence at the point it's checked (user level, then role level).
|
|
331
|
+
|
|
332
|
+
## Role Inheritance
|
|
333
|
+
|
|
334
|
+
Roles can inherit permissions from other roles.
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
// Create role hierarchy
|
|
338
|
+
await perms.createRole('member', 'Basic user', 1);
|
|
339
|
+
await perms.createRole('moderator', 'Moderator', 5);
|
|
340
|
+
await perms.createRole('admin', 'Administrator', 10);
|
|
341
|
+
|
|
342
|
+
// Assign permissions
|
|
343
|
+
await perms.assignPermission('page.home', 'member', 'role');
|
|
344
|
+
await perms.assignPermission('page.profile', 'member', 'role');
|
|
345
|
+
|
|
346
|
+
// Moderator inherits member permissions + has more
|
|
347
|
+
await perms.manager.setRoleInheritance('moderator', 'member');
|
|
348
|
+
await perms.assignPermission('page.moderation', 'moderator', 'role');
|
|
349
|
+
|
|
350
|
+
// Admin inherits moderator (which inherits member)
|
|
351
|
+
await perms.manager.setRoleInheritance('admin', 'moderator');
|
|
352
|
+
await perms.assignPermission('page.admin', 'admin', 'role');
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Result:** Admin has permissions from member + moderator + admin
|
|
356
|
+
|
|
357
|
+
**Circular Inheritance:** The system prevents circular inheritance and will throw `CircularInheritanceError`.
|
|
358
|
+
|
|
359
|
+
## Redis Caching
|
|
360
|
+
|
|
361
|
+
For production environments with multiple instances, use Redis for distributed caching.
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
import { createClient } from 'redis';
|
|
365
|
+
|
|
366
|
+
const redisClient = createClient({
|
|
367
|
+
url: 'redis://localhost:6379'
|
|
368
|
+
});
|
|
369
|
+
await redisClient.connect();
|
|
370
|
+
|
|
371
|
+
const perms = createPermissionSystem(prisma, {
|
|
372
|
+
redis: redisClient,
|
|
373
|
+
enableCache: true,
|
|
374
|
+
cacheTTL: 300, // 5 minutes
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Cache Keys Format:**
|
|
379
|
+
- User permissions: `v-perms:user:{userId}:{permissionKey}`
|
|
380
|
+
- Role permissions: `v-perms:role:{roleId}:{permissionKey}`
|
|
381
|
+
|
|
382
|
+
**When to Invalidate Cache:**
|
|
383
|
+
- After assigning/removing permissions: `invalidateUserCache(userId)` or `invalidateRoleCache(roleId)`
|
|
384
|
+
- After role changes: `invalidateRoleCache(roleId)`
|
|
385
|
+
- After role inheritance changes: invalidate both roles
|
|
386
|
+
|
|
387
|
+
## Error Handling
|
|
388
|
+
|
|
389
|
+
The package throws descriptive errors instead of returning `false`.
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
import { errors } from '@faryzal2020/v-perms';
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
await perms.assignRole('nonexistent-role', userId);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (error instanceof errors.RoleNotFoundError) {
|
|
398
|
+
console.error('Role not found:', error.details.roleId);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Error Types:**
|
|
404
|
+
- `UserNotFoundError`
|
|
405
|
+
- `RoleNotFoundError`
|
|
406
|
+
- `PermissionNotFoundError`
|
|
407
|
+
- `RoleAlreadyAssignedError`
|
|
408
|
+
- `PermissionAlreadyExistsError`
|
|
409
|
+
- `RoleAlreadyExistsError`
|
|
410
|
+
- `CircularInheritanceError`
|
|
411
|
+
|
|
412
|
+
## Debug Logging
|
|
413
|
+
|
|
414
|
+
Enable debug logging to see what's happening under the hood.
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
const perms = createPermissionSystem(prisma, {
|
|
418
|
+
debug: true,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Or enable later
|
|
422
|
+
perms.logger.enable();
|
|
423
|
+
|
|
424
|
+
// Disable
|
|
425
|
+
perms.logger.disable();
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Log Output:**
|
|
429
|
+
```
|
|
430
|
+
[v-perms:debug] checkPermission: user-123 endpoint.users.list
|
|
431
|
+
[v-perms:debug] Cache miss
|
|
432
|
+
[v-perms:debug] User roles (with inheritance): ['admin', 'member']
|
|
433
|
+
[v-perms:debug] Role wildcard match: admin endpoint.* true
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Common Patterns
|
|
437
|
+
|
|
438
|
+
### Middleware (Express Example)
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
function requirePermission(permission) {
|
|
442
|
+
return async (req, res, next) => {
|
|
443
|
+
const userId = req.user?.id;
|
|
444
|
+
|
|
445
|
+
if (!userId) {
|
|
446
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
const hasPermission = await perms.can(userId, permission);
|
|
451
|
+
|
|
452
|
+
if (!hasPermission) {
|
|
453
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
next();
|
|
457
|
+
} catch (error) {
|
|
458
|
+
console.error('Permission check error:', error);
|
|
459
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Usage
|
|
465
|
+
app.get('/api/users',
|
|
466
|
+
authenticate,
|
|
467
|
+
requirePermission('endpoint.users.list'),
|
|
468
|
+
async (req, res) => {
|
|
469
|
+
// Handler
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Assign Default Role on User Registration
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
async function registerUser(userData) {
|
|
478
|
+
const user = await prisma.user.create({
|
|
479
|
+
data: userData,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Get default role
|
|
483
|
+
const defaultRole = await prisma.role.findFirst({
|
|
484
|
+
where: { isDefault: true },
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
if (defaultRole) {
|
|
488
|
+
await perms.assignRole(defaultRole.id, user.id);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return user;
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Seed Initial Permissions
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
async function seedPermissions() {
|
|
499
|
+
const permissions = [
|
|
500
|
+
{ key: 'page.home', desc: 'Home page', cat: 'page' },
|
|
501
|
+
{ key: 'page.admin', desc: 'Admin dashboard', cat: 'page' },
|
|
502
|
+
{ key: 'endpoint.users.list', desc: 'List users', cat: 'endpoint' },
|
|
503
|
+
{ key: 'endpoint.users.create', desc: 'Create user', cat: 'endpoint' },
|
|
504
|
+
{ key: 'endpoint.users.delete', desc: 'Delete user', cat: 'endpoint' },
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
for (const perm of permissions) {
|
|
508
|
+
try {
|
|
509
|
+
await perms.createPermission(perm.key, perm.desc, perm.cat);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
// Permission already exists, skip
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## License
|
|
518
|
+
|
|
519
|
+
MIT
|
|
520
|
+
|
|
521
|
+
## Contributing
|
|
522
|
+
|
|
523
|
+
Contributions welcome! Please open an issue or PR.
|
|
524
|
+
|
|
525
|
+
## Support
|
|
526
|
+
|
|
527
|
+
For issues and questions, please use GitHub Issues.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@faryzal2020/v-perms",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Minimal, flexible role-based permission system for JavaScript/Bun.js applications with Prisma",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"prepublishOnly": "echo 'Ready to publish'"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"permissions",
|
|
17
|
+
"authorization",
|
|
18
|
+
"rbac",
|
|
19
|
+
"roles",
|
|
20
|
+
"access-control",
|
|
21
|
+
"prisma",
|
|
22
|
+
"bun",
|
|
23
|
+
"nodejs"
|
|
24
|
+
],
|
|
25
|
+
"author": "faryzal2020",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/faryzal2020/v-perms"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@prisma/client": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"redis": "^4.0.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0",
|
|
39
|
+
"bun": ">=1.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|