role-permission-engine 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/CHANGELOG.md +139 -0
- package/LICENSE +21 -0
- package/README.md +682 -0
- package/dist/index.cjs.js +2558 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.esm.js +2548 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/utils.cjs.js +389 -0
- package/dist/utils.cjs.js.map +1 -0
- package/dist/utils.esm.js +385 -0
- package/dist/utils.esm.js.map +1 -0
- package/package.json +106 -0
- package/types/index.d.ts +229 -0
- package/types/utils.d.ts +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# role-permission-engine
|
|
2
|
+
|
|
3
|
+
> A flexible **Role-Based Access Control (RBAC)** and **Permission-Based Access Control (PBAC)**
|
|
4
|
+
> engine for React applications with route protection, component guards, hooks,
|
|
5
|
+
> utilities, and TypeScript support.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/role-permission-engine)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
[](./src/__tests__)
|
|
10
|
+
|
|
11
|
+
🌐 **[Interactive Documentation & Live Demo](https://role-permission-engine.pages.dev/)**
|
|
12
|
+
|
|
13
|
+
A lightweight, flexible **role and permission-based route guard and UI gate** for React applications.
|
|
14
|
+
|
|
15
|
+
- ✅ **React Router v5 & v6** dual support
|
|
16
|
+
- ✅ **Role-based** and **permission-based** access control
|
|
17
|
+
- ✅ **AND / OR logic** for multi-value checks
|
|
18
|
+
- ✅ **Wildcard permissions** (`"*"`, `"read:*"`)
|
|
19
|
+
- ✅ **TypeScript declarations** included
|
|
20
|
+
- ✅ **Zero runtime dependencies** (only peer deps: React, React Router)
|
|
21
|
+
- ✅ **Full JSDoc** on every exported API
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Quick Start](#quick-start)
|
|
29
|
+
- [API Reference](#api-reference)
|
|
30
|
+
- [PermissionProvider](#permissionprovider)
|
|
31
|
+
- [BlockRoute](#blockroute)
|
|
32
|
+
- [PermissionGate](#permissiongate)
|
|
33
|
+
- [withPermission (HOC)](#withpermission)
|
|
34
|
+
- [usePermission](#usepermission)
|
|
35
|
+
- [usePermissionContext](#usepermissioncontext)
|
|
36
|
+
- [hasRole](#hasrole)
|
|
37
|
+
- [hasPermission](#haspermission)
|
|
38
|
+
- [checkAccess](#checkaccess)
|
|
39
|
+
- [Logic Operators](#logic-operators)
|
|
40
|
+
- [Wildcard Permissions](#wildcard-permissions)
|
|
41
|
+
- [React Router v5 Usage](#react-router-v5-usage)
|
|
42
|
+
- [Backend Subpath Usage](#backend-subpath-usage)
|
|
43
|
+
- [TypeScript](#typescript)
|
|
44
|
+
- [Examples](#examples)
|
|
45
|
+
- [Contributing](#contributing)
|
|
46
|
+
- [License](#license)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install role-permission-engine
|
|
54
|
+
# or
|
|
55
|
+
yarn add role-permission-engine
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Peer dependencies** (install these separately if not already present):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install react react-dom react-router-dom
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
### 1. Wrap your app with `PermissionProvider`
|
|
69
|
+
|
|
70
|
+
```jsx
|
|
71
|
+
// src/main.jsx (or App.jsx)
|
|
72
|
+
import { PermissionProvider } from 'role-permission-engine';
|
|
73
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
74
|
+
|
|
75
|
+
function App() {
|
|
76
|
+
// Fetch your user from your auth system
|
|
77
|
+
const user = { roles: ['editor'], permissions: ['write:posts', 'read:users'] };
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<PermissionProvider
|
|
81
|
+
roles={user.roles}
|
|
82
|
+
permissions={user.permissions}
|
|
83
|
+
user={user}
|
|
84
|
+
isAuthenticated={true}
|
|
85
|
+
>
|
|
86
|
+
<BrowserRouter>
|
|
87
|
+
<AppRoutes />
|
|
88
|
+
</BrowserRouter>
|
|
89
|
+
</PermissionProvider>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Protect routes with `BlockRoute`
|
|
95
|
+
|
|
96
|
+
```jsx
|
|
97
|
+
// src/AppRoutes.jsx (React Router v6)
|
|
98
|
+
import { Routes, Route } from 'react-router-dom';
|
|
99
|
+
import { BlockRoute } from 'role-permission-engine';
|
|
100
|
+
|
|
101
|
+
function AppRoutes() {
|
|
102
|
+
return (
|
|
103
|
+
<Routes>
|
|
104
|
+
<Route path="/" element={<HomePage />} />
|
|
105
|
+
|
|
106
|
+
{/* Only 'admin' role can access /admin */}
|
|
107
|
+
<Route
|
|
108
|
+
path="/admin"
|
|
109
|
+
element={
|
|
110
|
+
<BlockRoute roles={['admin']} redirectTo="/unauthorized">
|
|
111
|
+
<AdminPage />
|
|
112
|
+
</BlockRoute>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
{/* Any 'editor' or 'admin' with write:posts permission */}
|
|
117
|
+
<Route
|
|
118
|
+
path="/posts/edit/:id"
|
|
119
|
+
element={
|
|
120
|
+
<BlockRoute
|
|
121
|
+
roles={['editor', 'admin']}
|
|
122
|
+
permissions={['write:posts']}
|
|
123
|
+
redirectTo="/unauthorized"
|
|
124
|
+
>
|
|
125
|
+
<EditPostPage />
|
|
126
|
+
</BlockRoute>
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
|
131
|
+
</Routes>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3. Gate UI elements with `PermissionGate`
|
|
137
|
+
|
|
138
|
+
```jsx
|
|
139
|
+
import { PermissionGate } from 'role-permission-engine';
|
|
140
|
+
|
|
141
|
+
function UserActions({ userId }) {
|
|
142
|
+
return (
|
|
143
|
+
<div>
|
|
144
|
+
{/* Always visible */}
|
|
145
|
+
<ViewProfileButton userId={userId} />
|
|
146
|
+
|
|
147
|
+
{/* Only visible to admins */}
|
|
148
|
+
<PermissionGate roles={['admin']}>
|
|
149
|
+
<DeleteUserButton userId={userId} />
|
|
150
|
+
</PermissionGate>
|
|
151
|
+
|
|
152
|
+
{/* Only visible to users with write:users permission */}
|
|
153
|
+
<PermissionGate
|
|
154
|
+
permissions={['write:users']}
|
|
155
|
+
fallback={<span className="badge">Read-only</span>}
|
|
156
|
+
>
|
|
157
|
+
<EditUserButton userId={userId} />
|
|
158
|
+
</PermissionGate>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `PermissionProvider`
|
|
171
|
+
|
|
172
|
+
Provides the current user's roles, permissions, and auth state to all child components.
|
|
173
|
+
|
|
174
|
+
**Must wrap any component that uses `BlockRoute`, `PermissionGate`, or `usePermission`.**
|
|
175
|
+
|
|
176
|
+
#### Props
|
|
177
|
+
|
|
178
|
+
| Prop | Type | Default | Description |
|
|
179
|
+
|-------------------|-------------------|---------|---------------------------------------------------------|
|
|
180
|
+
| `roles` | `string[]` | `[]` | The current user's roles. |
|
|
181
|
+
| `permissions` | `string[]` | `[]` | The current user's permissions. |
|
|
182
|
+
| `user` | `object \| null` | `null` | The raw user object (any shape, for your own use). |
|
|
183
|
+
| `isAuthenticated` | `boolean` | `false` | Whether the user is authenticated. |
|
|
184
|
+
| `isLoading` | `boolean` | `false` | Set to `true` while auth state is being resolved. |
|
|
185
|
+
| `children` | `ReactNode` | — | **Required.** The component tree to provide context to. |
|
|
186
|
+
|
|
187
|
+
#### Example
|
|
188
|
+
|
|
189
|
+
```jsx
|
|
190
|
+
<PermissionProvider
|
|
191
|
+
roles={['admin', 'editor']}
|
|
192
|
+
permissions={['read:users', 'write:posts']}
|
|
193
|
+
user={{ id: 'u_123', name: 'Alice' }}
|
|
194
|
+
isAuthenticated={true}
|
|
195
|
+
isLoading={false}
|
|
196
|
+
>
|
|
197
|
+
{/* your app */}
|
|
198
|
+
</PermissionProvider>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### `BlockRoute`
|
|
204
|
+
|
|
205
|
+
A route guard that **redirects** unauthorized users. Compatible with **React Router v5 and v6**.
|
|
206
|
+
|
|
207
|
+
#### Props
|
|
208
|
+
|
|
209
|
+
| Prop | Type | Default | Description |
|
|
210
|
+
|---------------------|---------------|--------------------|-----------------------------------------------------------------------------|
|
|
211
|
+
| `children` | `ReactNode` | — | **Required.** Content to render when access is granted. |
|
|
212
|
+
| `roles` | `string[]` | `[]` | Required roles. Evaluated with `roleLogic`. |
|
|
213
|
+
| `permissions` | `string[]` | `[]` | Required permissions. Evaluated with `permissionLogic`. |
|
|
214
|
+
| `roleLogic` | `"any"\|"all"`| `"any"` | `"any"` = OR, `"all"` = AND logic for roles. |
|
|
215
|
+
| `permissionLogic` | `"any"\|"all"`| `"any"` | `"any"` = OR, `"all"` = AND logic for permissions. |
|
|
216
|
+
| `redirectTo` | `string` | `"/unauthorized"` | Path to redirect to when access is denied. |
|
|
217
|
+
| `loadingComponent` | `ReactNode` | `null` | Renders while `isLoading` is `true`. Defaults to `null` (nothing). |
|
|
218
|
+
| `replace` | `boolean` | `true` | Whether to replace the history entry (prevents Back button to blocked page).|
|
|
219
|
+
| `state` | `any` | `undefined` | Location state passed to the redirect (e.g. `{ from: location }`). |
|
|
220
|
+
|
|
221
|
+
#### Example — React Router v6
|
|
222
|
+
|
|
223
|
+
```jsx
|
|
224
|
+
<Route
|
|
225
|
+
path="/settings"
|
|
226
|
+
element={
|
|
227
|
+
<BlockRoute
|
|
228
|
+
roles={['admin']}
|
|
229
|
+
permissions={['manage:settings']}
|
|
230
|
+
permissionLogic="any"
|
|
231
|
+
redirectTo="/login"
|
|
232
|
+
loadingComponent={<Spinner />}
|
|
233
|
+
>
|
|
234
|
+
<SettingsPage />
|
|
235
|
+
</BlockRoute>
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### Example — Preserve redirect "from" location
|
|
241
|
+
|
|
242
|
+
```jsx
|
|
243
|
+
import { useLocation } from 'react-router-dom';
|
|
244
|
+
|
|
245
|
+
function AuthRequired({ children }) {
|
|
246
|
+
const location = useLocation();
|
|
247
|
+
return (
|
|
248
|
+
<BlockRoute
|
|
249
|
+
roles={['user']}
|
|
250
|
+
redirectTo="/login"
|
|
251
|
+
state={{ from: location }} // login page can read this to redirect back
|
|
252
|
+
>
|
|
253
|
+
{children}
|
|
254
|
+
</BlockRoute>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### `PermissionGate`
|
|
262
|
+
|
|
263
|
+
Conditionally **renders** or **hides** content based on roles/permissions.
|
|
264
|
+
Unlike `BlockRoute`, this never redirects — it simply shows or hides its children.
|
|
265
|
+
|
|
266
|
+
#### Props
|
|
267
|
+
|
|
268
|
+
| Prop | Type | Default | Description |
|
|
269
|
+
|--------------------|---------------|---------|----------------------------------------------------------------------|
|
|
270
|
+
| `children` | `ReactNode` | — | **Required.** Content shown when access is granted. |
|
|
271
|
+
| `roles` | `string[]` | `[]` | Required roles. |
|
|
272
|
+
| `permissions` | `string[]` | `[]` | Required permissions. |
|
|
273
|
+
| `roleLogic` | `"any"\|"all"`| `"any"` | Logic for role evaluation. |
|
|
274
|
+
| `permissionLogic` | `"any"\|"all"`| `"any"` | Logic for permission evaluation. |
|
|
275
|
+
| `fallback` | `ReactNode` | `null` | Content shown when access is denied. Defaults to `null`. |
|
|
276
|
+
| `loadingComponent` | `ReactNode` | `null` | Content shown while `isLoading` is `true`. |
|
|
277
|
+
| `negate` | `boolean` | `false` | When `true`, inverts the logic (shows children when access is denied)|
|
|
278
|
+
|
|
279
|
+
#### Example — With fallback
|
|
280
|
+
|
|
281
|
+
```jsx
|
|
282
|
+
<PermissionGate
|
|
283
|
+
roles={['admin']}
|
|
284
|
+
fallback={<p>You need admin access to see this.</p>}
|
|
285
|
+
>
|
|
286
|
+
<AdminControls />
|
|
287
|
+
</PermissionGate>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Example — Negate (show only to guests)
|
|
291
|
+
|
|
292
|
+
```jsx
|
|
293
|
+
// Show login button ONLY when user does NOT have 'user' role
|
|
294
|
+
<PermissionGate roles={['user']} negate>
|
|
295
|
+
<LoginButton />
|
|
296
|
+
</PermissionGate>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
### `withPermission`
|
|
302
|
+
|
|
303
|
+
A Higher Order Component (HOC) that wraps and protects a component.
|
|
304
|
+
Ideal for wrapping class-based components, or when you prefer HOC composition over JSX-based nesting.
|
|
305
|
+
|
|
306
|
+
#### Options
|
|
307
|
+
|
|
308
|
+
| Option | Type | Default | Description |
|
|
309
|
+
|--------------------|------------------|---------|-------------------------------------------------------------------------|
|
|
310
|
+
| `roles` | `string[]` | `[]` | Required roles. |
|
|
311
|
+
| `permissions` | `string[]` | `[]` | Required permissions. |
|
|
312
|
+
| `roleLogic` | `"any"\|"all"` | `"any"` | Logic for role evaluation. |
|
|
313
|
+
| `permissionLogic` | `"any"\|"all"` | `"any"` | Logic for permission evaluation. |
|
|
314
|
+
| `fallback` | `ReactNode` | `null` | Content shown when access is denied. Defaults to `null`. |
|
|
315
|
+
| `loadingComponent` | `ReactNode` | `null` | Content shown while `isLoading` is `true`. |
|
|
316
|
+
| `negate` | `boolean` | `false` | When `true`, inverts the logic (shows component when access is denied). |
|
|
317
|
+
|
|
318
|
+
#### Example
|
|
319
|
+
|
|
320
|
+
```jsx
|
|
321
|
+
import { withPermission } from 'role-permission-engine';
|
|
322
|
+
|
|
323
|
+
function Dashboard(props) {
|
|
324
|
+
return <div>Welcome, {props.username}!</div>;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Wrap and protect Dashboard
|
|
328
|
+
const ProtectedDashboard = withPermission(Dashboard, {
|
|
329
|
+
roles: ['admin', 'manager'],
|
|
330
|
+
roleLogic: 'any',
|
|
331
|
+
fallback: <p>Access Denied</p>,
|
|
332
|
+
loadingComponent: <p>Loading...</p>
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Render the wrapped component and pass props normally
|
|
336
|
+
<ProtectedDashboard username="Alice" />
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
### `usePermission`
|
|
342
|
+
|
|
343
|
+
A React hook for **programmatic** permission checking.
|
|
344
|
+
|
|
345
|
+
#### Signature
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
function usePermission(options?: {
|
|
349
|
+
roles?: string[];
|
|
350
|
+
permissions?: string[];
|
|
351
|
+
roleLogic?: 'any' | 'all';
|
|
352
|
+
permissionLogic?: 'any' | 'all';
|
|
353
|
+
}): {
|
|
354
|
+
allowed: boolean;
|
|
355
|
+
denied: boolean;
|
|
356
|
+
isLoading: boolean;
|
|
357
|
+
isAuthenticated: boolean;
|
|
358
|
+
reason: string;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Example
|
|
363
|
+
|
|
364
|
+
```jsx
|
|
365
|
+
import { usePermission } from 'role-permission-engine';
|
|
366
|
+
|
|
367
|
+
function PublishButton() {
|
|
368
|
+
const { allowed, isLoading } = usePermission({
|
|
369
|
+
roles: ['editor', 'admin'],
|
|
370
|
+
permissions: ['publish:posts'],
|
|
371
|
+
roleLogic: 'any',
|
|
372
|
+
permissionLogic: 'any',
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (isLoading) return null;
|
|
376
|
+
if (!allowed) return <span>Cannot publish</span>;
|
|
377
|
+
return <button>Publish Post</button>;
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### `usePermissionContext`
|
|
384
|
+
|
|
385
|
+
Returns the raw permission context value. Useful for accessing the user object or auth state directly.
|
|
386
|
+
|
|
387
|
+
```jsx
|
|
388
|
+
import { usePermissionContext } from 'role-permission-engine';
|
|
389
|
+
|
|
390
|
+
function UserGreeting() {
|
|
391
|
+
const { user, roles, isAuthenticated } = usePermissionContext();
|
|
392
|
+
|
|
393
|
+
if (!isAuthenticated) return <p>Welcome, guest!</p>;
|
|
394
|
+
return <p>Hello, {user?.name}! Roles: {roles.join(', ')}</p>;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
### `hasRole`
|
|
401
|
+
|
|
402
|
+
Pure utility function (no React required). Checks if a set of user roles satisfies the required roles.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
function hasRole(
|
|
406
|
+
userRoles: string[],
|
|
407
|
+
requiredRoles: string[],
|
|
408
|
+
logic?: 'any' | 'all'
|
|
409
|
+
): { allowed: boolean; reason: string }
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
import { hasRole } from 'role-permission-engine';
|
|
414
|
+
|
|
415
|
+
hasRole(['editor'], ['admin', 'editor'], 'any');
|
|
416
|
+
// → { allowed: true, reason: 'User has at least one required role.' }
|
|
417
|
+
|
|
418
|
+
hasRole(['editor'], ['admin', 'editor'], 'all');
|
|
419
|
+
// → { allowed: false, reason: 'User is missing required roles: admin' }
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### `hasPermission`
|
|
425
|
+
|
|
426
|
+
Pure utility. Checks permissions with wildcard support.
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
function hasPermission(
|
|
430
|
+
userPermissions: string[],
|
|
431
|
+
requiredPermissions: string[],
|
|
432
|
+
logic?: 'any' | 'all'
|
|
433
|
+
): { allowed: boolean; reason: string }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
import { hasPermission } from 'role-permission-engine';
|
|
438
|
+
|
|
439
|
+
// Wildcard
|
|
440
|
+
hasPermission(['*'], ['read:users', 'delete:posts'], 'all');
|
|
441
|
+
// → { allowed: true, reason: 'User has wildcard permission (*) — full access granted.' }
|
|
442
|
+
|
|
443
|
+
// Namespace wildcard
|
|
444
|
+
hasPermission(['read:*'], ['read:users', 'read:posts'], 'all');
|
|
445
|
+
// → { allowed: true, ... }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### `checkAccess`
|
|
451
|
+
|
|
452
|
+
Combined role + permission check in one call.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
function checkAccess(options: {
|
|
456
|
+
userRoles?: string[];
|
|
457
|
+
userPermissions?: string[];
|
|
458
|
+
requiredRoles?: string[];
|
|
459
|
+
requiredPermissions?: string[];
|
|
460
|
+
roleLogic?: 'any' | 'all';
|
|
461
|
+
permissionLogic?: 'any' | 'all';
|
|
462
|
+
}): { allowed: boolean; reason: string }
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
```js
|
|
466
|
+
import { checkAccess } from 'role-permission-engine';
|
|
467
|
+
|
|
468
|
+
checkAccess({
|
|
469
|
+
userRoles: ['editor'],
|
|
470
|
+
userPermissions: ['write:posts'],
|
|
471
|
+
requiredRoles: ['editor'],
|
|
472
|
+
requiredPermissions: ['write:posts'],
|
|
473
|
+
});
|
|
474
|
+
// → { allowed: true, reason: 'Access granted.' }
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Logic Operators
|
|
480
|
+
|
|
481
|
+
All multi-value checks support two operators:
|
|
482
|
+
|
|
483
|
+
| Operator | Meaning | Example |
|
|
484
|
+
|----------|-----------------------------------------|-------------------------------------------------------------------------|
|
|
485
|
+
| `"any"` | User needs **at least one** match (OR) | `roles={['admin', 'editor']}` + `roleLogic="any"` — admin **or** editor |
|
|
486
|
+
| `"all"` | User needs **every** match (AND) | `roles={['admin', 'editor']}` + `roleLogic="all"` — admin **and** editor|
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Wildcard Permissions
|
|
491
|
+
|
|
492
|
+
| Pattern | Matches |
|
|
493
|
+
|-----------------|-------------------------------------------------------------|
|
|
494
|
+
| `"*"` | Everything — full superuser access |
|
|
495
|
+
| `"read:*"` | All `read:` permissions (`read:users`, `read:posts`, etc.) |
|
|
496
|
+
| `"write:posts"` | Only `write:posts` exactly |
|
|
497
|
+
|
|
498
|
+
```jsx
|
|
499
|
+
// Superadmin with wildcard
|
|
500
|
+
<PermissionProvider permissions={['*']}>
|
|
501
|
+
{/* User can access all routes / gates */}
|
|
502
|
+
</PermissionProvider>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## React Router v5 Usage
|
|
508
|
+
|
|
509
|
+
The `BlockRoute` component auto-detects whether React Router v5 or v6 is installed.
|
|
510
|
+
|
|
511
|
+
**React Router v5:**
|
|
512
|
+
|
|
513
|
+
```jsx
|
|
514
|
+
import { Switch, Route } from 'react-router-dom'; // v5
|
|
515
|
+
|
|
516
|
+
<Switch>
|
|
517
|
+
<Route
|
|
518
|
+
path="/admin"
|
|
519
|
+
render={() => (
|
|
520
|
+
<BlockRoute roles={['admin']} redirectTo="/unauthorized">
|
|
521
|
+
<AdminPage />
|
|
522
|
+
</BlockRoute>
|
|
523
|
+
)}
|
|
524
|
+
/>
|
|
525
|
+
</Switch>
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Backend Subpath Usage
|
|
531
|
+
|
|
532
|
+
If you are building a backend project (like a Node.js/Express service) and want to use the pure permission utilities, you can import them from `role-permission-engine/utils`.
|
|
533
|
+
|
|
534
|
+
This entrypoint **has zero dependency on React or React Router**, meaning you can import and run it in a pure Node.js environment without any runtime errors or peer-dependency warning prompts.
|
|
535
|
+
|
|
536
|
+
### Example
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
import { checkAccess } from 'role-permission-engine/utils';
|
|
540
|
+
|
|
541
|
+
// Express middleware
|
|
542
|
+
export function requirePermissions(required = {}) {
|
|
543
|
+
return (req, res, next) => {
|
|
544
|
+
const userRoles = req.user?.roles || [];
|
|
545
|
+
const userPermissions = req.user?.permissions || [];
|
|
546
|
+
|
|
547
|
+
const { allowed, reason } = checkAccess({
|
|
548
|
+
userRoles,
|
|
549
|
+
userPermissions,
|
|
550
|
+
requiredRoles: required.roles,
|
|
551
|
+
requiredPermissions: required.permissions
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
if (!allowed) {
|
|
555
|
+
return res.status(403).json({ error: 'Forbidden', reason });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
next();
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## TypeScript
|
|
566
|
+
|
|
567
|
+
Full TypeScript declarations are included. All types are automatically available when you import from `role-permission-engine`:
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
import {
|
|
571
|
+
BlockRoute,
|
|
572
|
+
BlockRouteProps,
|
|
573
|
+
PermissionGate,
|
|
574
|
+
PermissionProvider,
|
|
575
|
+
usePermission,
|
|
576
|
+
UsePermissionOptions,
|
|
577
|
+
UsePermissionResult,
|
|
578
|
+
hasRole,
|
|
579
|
+
hasPermission,
|
|
580
|
+
checkAccess,
|
|
581
|
+
Role,
|
|
582
|
+
Permission,
|
|
583
|
+
LogicOperator,
|
|
584
|
+
PermissionResult,
|
|
585
|
+
} from 'role-permission-engine';
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Examples
|
|
591
|
+
|
|
592
|
+
### Full app setup with async auth
|
|
593
|
+
|
|
594
|
+
```jsx
|
|
595
|
+
import { useState, useEffect } from 'react';
|
|
596
|
+
import { PermissionProvider, BlockRoute } from 'role-permission-engine';
|
|
597
|
+
|
|
598
|
+
function App() {
|
|
599
|
+
const [auth, setAuth] = useState({ roles: [], permissions: [], user: null });
|
|
600
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
601
|
+
|
|
602
|
+
useEffect(() => {
|
|
603
|
+
fetchCurrentUser()
|
|
604
|
+
.then((user) => setAuth({ roles: user.roles, permissions: user.permissions, user }))
|
|
605
|
+
.finally(() => setIsLoading(false));
|
|
606
|
+
}, []);
|
|
607
|
+
|
|
608
|
+
return (
|
|
609
|
+
<PermissionProvider
|
|
610
|
+
roles={auth.roles}
|
|
611
|
+
permissions={auth.permissions}
|
|
612
|
+
user={auth.user}
|
|
613
|
+
isAuthenticated={!!auth.user}
|
|
614
|
+
isLoading={isLoading}
|
|
615
|
+
>
|
|
616
|
+
<BrowserRouter>
|
|
617
|
+
<Routes>
|
|
618
|
+
<Route path="/" element={<Home />} />
|
|
619
|
+
<Route
|
|
620
|
+
path="/dashboard"
|
|
621
|
+
element={
|
|
622
|
+
<BlockRoute
|
|
623
|
+
roles={['user', 'admin']}
|
|
624
|
+
redirectTo="/login"
|
|
625
|
+
loadingComponent={<FullPageSpinner />}
|
|
626
|
+
>
|
|
627
|
+
<Dashboard />
|
|
628
|
+
</BlockRoute>
|
|
629
|
+
}
|
|
630
|
+
/>
|
|
631
|
+
</Routes>
|
|
632
|
+
</BrowserRouter>
|
|
633
|
+
</PermissionProvider>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Granular UI control
|
|
639
|
+
|
|
640
|
+
```jsx
|
|
641
|
+
import { PermissionGate, usePermission } from 'role-permission-engine';
|
|
642
|
+
|
|
643
|
+
function DocumentPage({ doc }) {
|
|
644
|
+
const { allowed: canExport } = usePermission({ permissions: ['export:documents'] });
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
<article>
|
|
648
|
+
<h1>{doc.title}</h1>
|
|
649
|
+
<p>{doc.body}</p>
|
|
650
|
+
|
|
651
|
+
{/* Show edit toolbar only to editors and admins */}
|
|
652
|
+
<PermissionGate roles={['editor', 'admin']}>
|
|
653
|
+
<EditToolbar docId={doc.id} />
|
|
654
|
+
</PermissionGate>
|
|
655
|
+
|
|
656
|
+
{/* Programmatic check */}
|
|
657
|
+
{canExport && <ExportButton docId={doc.id} />}
|
|
658
|
+
|
|
659
|
+
{/* Show "Request Access" to users without write permission */}
|
|
660
|
+
<PermissionGate permissions={['write:documents']} negate>
|
|
661
|
+
<RequestAccessButton />
|
|
662
|
+
</PermissionGate>
|
|
663
|
+
</article>
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## Contributing
|
|
671
|
+
|
|
672
|
+
1. Fork the repository
|
|
673
|
+
2. Create your feature branch: `git checkout -b feat/your-feature`
|
|
674
|
+
3. Run tests: `npm test`
|
|
675
|
+
4. Submit a pull request
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## License
|
|
680
|
+
|
|
681
|
+
This project is licensed under the MIT License.
|
|
682
|
+
See the [LICENSE](LICENSE) file for details.
|