react-auth-gate 0.0.1
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 +653 -0
- package/dist/core/ruleEngine.d.ts +47 -0
- package/dist/core/ruleEngine.d.ts.map +1 -0
- package/dist/core/ruleEngine.js +138 -0
- package/dist/core/types.d.ts +122 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/devtools/DevPanel.d.ts +13 -0
- package/dist/devtools/DevPanel.d.ts.map +1 -0
- package/dist/devtools/DevPanel.js +308 -0
- package/dist/devtools/DevStore.d.ts +80 -0
- package/dist/devtools/DevStore.d.ts.map +1 -0
- package/dist/devtools/DevStore.js +152 -0
- package/dist/devtools/PermissionsRoot.d.ts +33 -0
- package/dist/devtools/PermissionsRoot.d.ts.map +1 -0
- package/dist/devtools/PermissionsRoot.js +46 -0
- package/dist/devtools/useDevRegister.d.ts +17 -0
- package/dist/devtools/useDevRegister.d.ts.map +1 -0
- package/dist/devtools/useDevRegister.js +29 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/react/Permissioned.d.ts +41 -0
- package/dist/react/Permissioned.d.ts.map +1 -0
- package/dist/react/Permissioned.js +29 -0
- package/dist/react/PermissionsGate.d.ts +79 -0
- package/dist/react/PermissionsGate.d.ts.map +1 -0
- package/dist/react/PermissionsGate.js +101 -0
- package/dist/react/PermissionsProvider.d.ts +39 -0
- package/dist/react/PermissionsProvider.d.ts.map +1 -0
- package/dist/react/PermissionsProvider.js +93 -0
- package/dist/react/ProtectedRoute.d.ts +80 -0
- package/dist/react/ProtectedRoute.d.ts.map +1 -0
- package/dist/react/ProtectedRoute.js +92 -0
- package/dist/react/usePermission.d.ts +50 -0
- package/dist/react/usePermission.d.ts.map +1 -0
- package/dist/react/usePermission.js +71 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 react-permissions-gate
|
|
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,653 @@
|
|
|
1
|
+
# ð react-auth-gate
|
|
2
|
+
|
|
3
|
+
A production-grade React authorization framework that centralizes **RBAC**, **PBAC**, **ABAC**, feature flags, and async permission checks into a clean, declarative API.
|
|
4
|
+
|
|
5
|
+
**Permission logic never lives inside components again.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/react-auth-gate)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## âĻ Features
|
|
14
|
+
|
|
15
|
+
- **ðŊ Declarative API** - Gate components with simple props
|
|
16
|
+
- **ð RBAC + PBAC + ABAC** - Role, Permission, and Attribute-based access control
|
|
17
|
+
- **⥠Async Support** - Check permissions against APIs in real-time
|
|
18
|
+
- **ðĐ Feature Flags** - Built-in feature flag support
|
|
19
|
+
- **ðĻ Framework Agnostic** - Works with any React app (Next.js, CRA, Vite, etc.)
|
|
20
|
+
- **ðĶ Tree-shakeable** - Zero runtime overhead for unused features
|
|
21
|
+
- **ð TypeScript First** - Fully typed with excellent IntelliSense
|
|
22
|
+
- **ð ïļ Dev Tools Panel** - **Killer feature**: Automatic permission debugging panel in development
|
|
23
|
+
- **ðŠķ Lightweight** - No heavy dependencies
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ð Quick Start
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install react-auth-gate
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { PermissionsRoot, PermissionsGate } from 'react-auth-gate';
|
|
39
|
+
|
|
40
|
+
// 1. Define your permission rules
|
|
41
|
+
const rules = {
|
|
42
|
+
'user.edit': ({ user, resource }) =>
|
|
43
|
+
user.role === 'admin' || user.id === resource.id,
|
|
44
|
+
'post.delete': ({ user, resource }) =>
|
|
45
|
+
user.id === resource.authorId,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// 2. Wrap your app
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<PermissionsRoot
|
|
52
|
+
user={currentUser}
|
|
53
|
+
roles={['editor']}
|
|
54
|
+
permissions={['post.create', 'post.edit']}
|
|
55
|
+
rules={rules}
|
|
56
|
+
flags={{ newUI: true }}
|
|
57
|
+
>
|
|
58
|
+
<YourApp />
|
|
59
|
+
</PermissionsRoot>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Use permission gates anywhere
|
|
64
|
+
function UserProfile({ user }) {
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
<h1>{user.name}</h1>
|
|
68
|
+
<PermissionsGate allow="user.edit" resource={user}>
|
|
69
|
+
<EditButton />
|
|
70
|
+
</PermissionsGate>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**That's it!** Your permissions are centralized, testable, and declarative.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ð Core Concepts
|
|
81
|
+
|
|
82
|
+
### Permission Rules
|
|
83
|
+
|
|
84
|
+
Rules are functions that determine access. They receive a context object and return a boolean (or Promise<boolean>).
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
type PermissionRule = (ctx: {
|
|
88
|
+
user: any; // Current user
|
|
89
|
+
resource?: any; // Resource being accessed
|
|
90
|
+
roles: string[]; // User's roles
|
|
91
|
+
permissions: string[]; // User's permissions
|
|
92
|
+
flags: Record<string, boolean>; // Feature flags
|
|
93
|
+
}) => boolean | Promise<boolean>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Rule Types
|
|
97
|
+
|
|
98
|
+
#### 1. **Role-Based (RBAC)**
|
|
99
|
+
```tsx
|
|
100
|
+
const rules = {
|
|
101
|
+
'admin.access': ({ roles }) => roles.includes('admin'),
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### 2. **Permission-Based (PBAC)**
|
|
106
|
+
```tsx
|
|
107
|
+
const rules = {
|
|
108
|
+
'post.create': ({ permissions }) => permissions.includes('post.create'),
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 3. **Attribute-Based (ABAC)**
|
|
113
|
+
```tsx
|
|
114
|
+
const rules = {
|
|
115
|
+
'user.edit': ({ user, resource }) =>
|
|
116
|
+
user.role === 'admin' || user.id === resource.id,
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### 4. **Async Rules**
|
|
121
|
+
```tsx
|
|
122
|
+
const rules = {
|
|
123
|
+
'subscription.premium': async ({ user }) => {
|
|
124
|
+
const subscription = await checkSubscriptionAPI(user.id);
|
|
125
|
+
return subscription.isPremium;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## ðŊ API Reference
|
|
133
|
+
|
|
134
|
+
### `<PermissionsRoot>`
|
|
135
|
+
|
|
136
|
+
The root provider component. Use this to wrap your app with automatic dev tools integration.
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<PermissionsRoot
|
|
140
|
+
user={currentUser}
|
|
141
|
+
roles={['admin', 'editor']}
|
|
142
|
+
permissions={['post.edit', 'post.delete']}
|
|
143
|
+
rules={rulesMap}
|
|
144
|
+
flags={{ newUI: true }}
|
|
145
|
+
enableDevTools={true} // default: auto-enabled in development
|
|
146
|
+
>
|
|
147
|
+
<App />
|
|
148
|
+
</PermissionsRoot>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Props:**
|
|
152
|
+
- `user` - Current authenticated user
|
|
153
|
+
- `roles?` - Array of role strings
|
|
154
|
+
- `permissions?` - Array of permission strings
|
|
155
|
+
- `rules?` - Map of named permission rules
|
|
156
|
+
- `flags?` - Feature flags object
|
|
157
|
+
- `enableDevTools?` - Enable/disable dev panel (default: auto in dev mode)
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### `<PermissionsGate>`
|
|
162
|
+
|
|
163
|
+
Declarative permission boundary component.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<PermissionsGate
|
|
167
|
+
allow="user.edit"
|
|
168
|
+
resource={user}
|
|
169
|
+
mode="hide"
|
|
170
|
+
fallback={<div>Access Denied</div>}
|
|
171
|
+
>
|
|
172
|
+
<EditButton />
|
|
173
|
+
</PermissionsGate>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Props:**
|
|
177
|
+
- `allow?` - Permission check (string, array, or function)
|
|
178
|
+
- `any?` - Array of permissions (OR logic)
|
|
179
|
+
- `all?` - Array of permissions (AND logic)
|
|
180
|
+
- `resource?` - Resource to check against
|
|
181
|
+
- `mode?` - `"hide"` (default) or `"disable"`
|
|
182
|
+
- `fallback?` - React node to show when denied
|
|
183
|
+
- `children` - Protected content
|
|
184
|
+
|
|
185
|
+
**Examples:**
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// Single permission
|
|
189
|
+
<PermissionsGate allow="admin.access">
|
|
190
|
+
<AdminPanel />
|
|
191
|
+
</PermissionsGate>
|
|
192
|
+
|
|
193
|
+
// Multiple permissions (any)
|
|
194
|
+
<PermissionsGate any={['admin', 'moderator']}>
|
|
195
|
+
<ModPanel />
|
|
196
|
+
</PermissionsGate>
|
|
197
|
+
|
|
198
|
+
// Multiple permissions (all)
|
|
199
|
+
<PermissionsGate all={['post.edit', 'post.publish']}>
|
|
200
|
+
<PublishButton />
|
|
201
|
+
</PermissionsGate>
|
|
202
|
+
|
|
203
|
+
// Disable mode
|
|
204
|
+
<PermissionsGate allow="post.delete" resource={post} mode="disable">
|
|
205
|
+
<DeleteButton />
|
|
206
|
+
</PermissionsGate>
|
|
207
|
+
|
|
208
|
+
// Inline rule
|
|
209
|
+
<PermissionsGate allow={({ user }) => user.verified}>
|
|
210
|
+
<VerifiedBadge />
|
|
211
|
+
</PermissionsGate>
|
|
212
|
+
|
|
213
|
+
// With fallback
|
|
214
|
+
<PermissionsGate allow="premium.feature" fallback={<UpgradePrompt />}>
|
|
215
|
+
<PremiumContent />
|
|
216
|
+
</PermissionsGate>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `usePermission()`
|
|
222
|
+
|
|
223
|
+
Hook for programmatic permission checks.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
const { allowed, loading } = usePermission('user.edit', user);
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<button disabled={!allowed || loading}>
|
|
230
|
+
{loading ? 'Checking...' : 'Edit'}
|
|
231
|
+
</button>
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Returns:**
|
|
236
|
+
- `allowed` - Boolean indicating if permission is granted
|
|
237
|
+
- `loading` - Boolean indicating if check is in progress
|
|
238
|
+
|
|
239
|
+
**Simpler version (no loading state):**
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
const canEdit = usePermissionValue('user.edit', user);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### `<Permissioned>`
|
|
248
|
+
|
|
249
|
+
Render-prop version for maximum control.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<Permissioned allow="post.edit" resource={post}>
|
|
253
|
+
{(allowed, loading) => (
|
|
254
|
+
<button disabled={!allowed || loading}>
|
|
255
|
+
{loading ? 'Checking...' : allowed ? 'Edit' : 'View Only'}
|
|
256
|
+
</button>
|
|
257
|
+
)}
|
|
258
|
+
</Permissioned>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### `<ProtectedRoute>`
|
|
264
|
+
|
|
265
|
+
Route protection component (framework-agnostic).
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
// React Router
|
|
269
|
+
<Route
|
|
270
|
+
path="/admin"
|
|
271
|
+
element={
|
|
272
|
+
<ProtectedRoute
|
|
273
|
+
allow="admin.access"
|
|
274
|
+
fallback={<Navigate to="/login" />}
|
|
275
|
+
>
|
|
276
|
+
<AdminDashboard />
|
|
277
|
+
</ProtectedRoute>
|
|
278
|
+
}
|
|
279
|
+
/>
|
|
280
|
+
|
|
281
|
+
// Next.js
|
|
282
|
+
function AdminPage() {
|
|
283
|
+
const router = useRouter();
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<ProtectedRoute
|
|
287
|
+
allow="admin"
|
|
288
|
+
onAccessDenied={() => router.push('/login')}
|
|
289
|
+
>
|
|
290
|
+
<AdminPanel />
|
|
291
|
+
</ProtectedRoute>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Props:**
|
|
297
|
+
- `allow` - Permission check
|
|
298
|
+
- `resource?` - Resource to check
|
|
299
|
+
- `fallback?` - Content when denied (default: unauthorized message)
|
|
300
|
+
- `onAccessDenied?` - Callback when access denied
|
|
301
|
+
- `children` - Protected content
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## ð ïļ Dev Tools Panel (The Killer Feature)
|
|
306
|
+
|
|
307
|
+
In development mode, **react-auth-gate** automatically renders a floating permission debugger.
|
|
308
|
+
|
|
309
|
+
### Features
|
|
310
|
+
|
|
311
|
+
â
**Live Permission Tracking** - See every permission check as it happens
|
|
312
|
+
â
**Pass/Fail Details** - Understand why checks succeed or fail
|
|
313
|
+
â
**Rule Inspection** - See which rules evaluated and their results
|
|
314
|
+
â
**Context Override** - Test different roles, permissions, and flags
|
|
315
|
+
â
**Real-time Simulation** - Toggle permissions without code changes
|
|
316
|
+
â
**Zero Configuration** - Appears automatically when using `PermissionsRoot`
|
|
317
|
+
|
|
318
|
+
### How to Use
|
|
319
|
+
|
|
320
|
+
1. Use `PermissionsRoot` instead of `PermissionsProvider`
|
|
321
|
+
2. Run your app in development mode
|
|
322
|
+
3. Click the ð icon in the bottom-right corner
|
|
323
|
+
|
|
324
|
+
### Panel Tabs
|
|
325
|
+
|
|
326
|
+
**1. Evaluations**
|
|
327
|
+
- Shows all permission checks in real-time
|
|
328
|
+
- Pass/fail status with rule details
|
|
329
|
+
- Timestamps and evaluation duration
|
|
330
|
+
- Resource information
|
|
331
|
+
|
|
332
|
+
**2. Overrides**
|
|
333
|
+
- Toggle roles on/off
|
|
334
|
+
- Add/remove permissions
|
|
335
|
+
- Enable/disable feature flags
|
|
336
|
+
- Test different scenarios instantly
|
|
337
|
+
|
|
338
|
+
**3. Context**
|
|
339
|
+
- View current user object
|
|
340
|
+
- See active roles and permissions
|
|
341
|
+
- Inspect feature flags
|
|
342
|
+
- Debug context values
|
|
343
|
+
|
|
344
|
+
### Screenshot
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
âââââââââââââââââââââââââââââââââââââââââââ
|
|
348
|
+
â ð Permissions Dev Panel [Clear] [â] â
|
|
349
|
+
âââââââââââââââââââââââââââââââââââââââââââĪ
|
|
350
|
+
â Evaluations (12) â Overrides â Context â
|
|
351
|
+
âââââââââââââââââââââââââââââââââââââââââââĪ
|
|
352
|
+
â â ALLOWED user.edit â
|
|
353
|
+
â user.edit: â (2.34ms) â
|
|
354
|
+
â Resource: { id: "123", name: "..." } â
|
|
355
|
+
â 10:45:23 AM â
|
|
356
|
+
â â
|
|
357
|
+
â â DENIED post.delete â
|
|
358
|
+
â post.delete: â (1.12ms) â
|
|
359
|
+
â 10:45:25 AM â
|
|
360
|
+
âââââââââââââââââââââââââââââââââââââââââââ
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## ð Common Patterns
|
|
366
|
+
|
|
367
|
+
### Resource Ownership
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
const rules = {
|
|
371
|
+
'resource.edit': ({ user, resource }) =>
|
|
372
|
+
user.role === 'admin' || user.id === resource.ownerId,
|
|
373
|
+
};
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Time-Based Access
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
const rules = {
|
|
380
|
+
'event.register': ({ resource }) => {
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
return now >= resource.registrationStart && now <= resource.registrationEnd;
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Hierarchical Permissions
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
const rules = {
|
|
391
|
+
'content.view': ({ permissions }) =>
|
|
392
|
+
permissions.includes('content.view') ||
|
|
393
|
+
permissions.includes('content.edit') ||
|
|
394
|
+
permissions.includes('content.admin'),
|
|
395
|
+
};
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Complex Business Logic
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
const rules = {
|
|
402
|
+
'order.cancel': ({ user, resource }) => {
|
|
403
|
+
// Can't cancel shipped orders
|
|
404
|
+
if (resource.status === 'shipped') return false;
|
|
405
|
+
|
|
406
|
+
// Customer can cancel within 24h
|
|
407
|
+
if (user.id === resource.customerId) {
|
|
408
|
+
const hoursSinceOrder = (Date.now() - resource.createdAt) / (1000 * 60 * 60);
|
|
409
|
+
return hoursSinceOrder < 24;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Admin can always cancel
|
|
413
|
+
return user.role === 'admin';
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## ð§Š Testing
|
|
421
|
+
|
|
422
|
+
Permission rules are pure functions, making them easy to test.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
import { rules } from './permissions';
|
|
426
|
+
|
|
427
|
+
describe('user.edit permission', () => {
|
|
428
|
+
it('allows admin to edit any user', () => {
|
|
429
|
+
const result = rules['user.edit']({
|
|
430
|
+
user: { id: '1', role: 'admin' },
|
|
431
|
+
resource: { id: '2' },
|
|
432
|
+
roles: ['admin'],
|
|
433
|
+
permissions: [],
|
|
434
|
+
flags: {},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(result).toBe(true);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('allows user to edit themselves', () => {
|
|
441
|
+
const result = rules['user.edit']({
|
|
442
|
+
user: { id: '1', role: 'user' },
|
|
443
|
+
resource: { id: '1' },
|
|
444
|
+
roles: ['user'],
|
|
445
|
+
permissions: [],
|
|
446
|
+
flags: {},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(result).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('denies user from editing others', () => {
|
|
453
|
+
const result = rules['user.edit']({
|
|
454
|
+
user: { id: '1', role: 'user' },
|
|
455
|
+
resource: { id: '2' },
|
|
456
|
+
roles: ['user'],
|
|
457
|
+
permissions: [],
|
|
458
|
+
flags: {},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(result).toBe(false);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## ðĶ Advanced Usage
|
|
469
|
+
|
|
470
|
+
### Custom Permission Provider
|
|
471
|
+
|
|
472
|
+
If you need custom integration without dev tools:
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
import { PermissionsProvider } from 'react-auth-gate';
|
|
476
|
+
|
|
477
|
+
<PermissionsProvider {...config}>
|
|
478
|
+
<App />
|
|
479
|
+
</PermissionsProvider>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Manual Dev Tools Integration
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
import { PermissionsProvider, DevPanel, useDevRegister } from 'react-auth-gate';
|
|
486
|
+
|
|
487
|
+
function Root() {
|
|
488
|
+
const registerEvaluation = useDevRegister();
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<PermissionsProvider {...config} onEvaluationRegister={registerEvaluation}>
|
|
492
|
+
<App />
|
|
493
|
+
<DevPanel />
|
|
494
|
+
</PermissionsProvider>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Direct Rule Engine Access
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
import { evaluatePermission, createPermissionContext } from 'react-auth-gate';
|
|
503
|
+
|
|
504
|
+
const context = createPermissionContext(user, resource, roles, permissions, flags);
|
|
505
|
+
const result = await evaluatePermission(check, context, rulesMap);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## ðĻ TypeScript Support
|
|
511
|
+
|
|
512
|
+
Fully typed with generics for your custom types:
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
interface User {
|
|
516
|
+
id: string;
|
|
517
|
+
role: 'admin' | 'user';
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
interface Post {
|
|
521
|
+
id: string;
|
|
522
|
+
authorId: string;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const rules: PermissionRulesMap<User, Post> = {
|
|
526
|
+
'post.edit': ({ user, resource }) => {
|
|
527
|
+
// Full type safety!
|
|
528
|
+
return user.role === 'admin' || user.id === resource?.authorId;
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## ð§ Framework Integration
|
|
536
|
+
|
|
537
|
+
### React Router
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
import { ProtectedRoute } from 'react-auth-gate';
|
|
541
|
+
import { Navigate } from 'react-router-dom';
|
|
542
|
+
|
|
543
|
+
<Route
|
|
544
|
+
path="/admin"
|
|
545
|
+
element={
|
|
546
|
+
<ProtectedRoute allow="admin" fallback={<Navigate to="/" />}>
|
|
547
|
+
<AdminPage />
|
|
548
|
+
</ProtectedRoute>
|
|
549
|
+
}
|
|
550
|
+
/>
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Next.js
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
// pages/admin.tsx
|
|
557
|
+
import { ProtectedRoute } from 'react-auth-gate';
|
|
558
|
+
import { useRouter } from 'next/router';
|
|
559
|
+
|
|
560
|
+
export default function AdminPage() {
|
|
561
|
+
const router = useRouter();
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
<ProtectedRoute
|
|
565
|
+
allow="admin"
|
|
566
|
+
onAccessDenied={() => router.push('/login')}
|
|
567
|
+
>
|
|
568
|
+
<AdminDashboard />
|
|
569
|
+
</ProtectedRoute>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Remix
|
|
575
|
+
|
|
576
|
+
```tsx
|
|
577
|
+
import { ProtectedRoute } from 'react-auth-gate';
|
|
578
|
+
import { useNavigate } from '@remix-run/react';
|
|
579
|
+
|
|
580
|
+
export default function Route() {
|
|
581
|
+
const navigate = useNavigate();
|
|
582
|
+
|
|
583
|
+
return (
|
|
584
|
+
<ProtectedRoute
|
|
585
|
+
allow="admin"
|
|
586
|
+
onAccessDenied={() => navigate('/login')}
|
|
587
|
+
>
|
|
588
|
+
<Content />
|
|
589
|
+
</ProtectedRoute>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## ðĪ FAQ
|
|
597
|
+
|
|
598
|
+
**Q: How is this different from checking permissions in components?**
|
|
599
|
+
A: All permission logic is centralized in the rules map. Components don't contain authorization logic, making them easier to maintain and test.
|
|
600
|
+
|
|
601
|
+
**Q: Can I use this with server-side auth?**
|
|
602
|
+
A: Yes! This library handles UI-level authorization. Your server should still validate permissions. This prevents unnecessary API calls and provides better UX.
|
|
603
|
+
|
|
604
|
+
**Q: Does this work with Next.js App Router?**
|
|
605
|
+
A: Yes! Use `"use client"` for components using the library. Server Components can check permissions differently.
|
|
606
|
+
|
|
607
|
+
**Q: What about bundle size?**
|
|
608
|
+
A: ~5KB gzipped. Tree-shakeable, so you only pay for what you use.
|
|
609
|
+
|
|
610
|
+
**Q: Can I use this without TypeScript?**
|
|
611
|
+
A: Yes, but TypeScript is recommended for the best experience.
|
|
612
|
+
|
|
613
|
+
**Q: How do I disable the dev panel in production?**
|
|
614
|
+
A: It's automatically disabled when `process.env.NODE_ENV === 'production'`.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## ðŊ Best Practices
|
|
619
|
+
|
|
620
|
+
1. â
**Centralize rules** - Define all rules in one place
|
|
621
|
+
2. â
**Keep rules pure** - No side effects in rule functions
|
|
622
|
+
3. â
**Test rules independently** - Rules are just functions
|
|
623
|
+
4. â
**Use TypeScript** - Get type safety for users and resources
|
|
624
|
+
5. â
**Async sparingly** - Async rules add latency
|
|
625
|
+
6. â
**Server-side validation** - Never trust client-side checks alone
|
|
626
|
+
7. â
**Use the dev panel** - Debug permissions visually
|
|
627
|
+
8. â
**Resource-based when possible** - More secure than role-only checks
|
|
628
|
+
9. â
**Fallback content** - Provide good UX when access is denied
|
|
629
|
+
10. â
**Name rules clearly** - Use dot notation: `resource.action`
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## ð License
|
|
634
|
+
|
|
635
|
+
MIT ÂĐ 2024
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## ð Contributing
|
|
640
|
+
|
|
641
|
+
Contributions are welcome! Please open an issue or PR.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## ð Links
|
|
646
|
+
|
|
647
|
+
- [GitHub Repository](https://github.com/klejdi94/react-auth-gate)
|
|
648
|
+
- [npm Package](https://www.npmjs.com/package/react-auth-gate)
|
|
649
|
+
- [Report Issues](https://github.com/klejdi94/react-auth-gate/issues)
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
**Built with âĪïļ for developers who value clean, maintainable code.**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Engine
|
|
3
|
+
*
|
|
4
|
+
* Core logic for evaluating permission rules against a context.
|
|
5
|
+
* Supports sync/async rules, string-based rules, and inline functions.
|
|
6
|
+
*/
|
|
7
|
+
import type { PermissionContext, PermissionRule, PermissionRulesMap, PermissionCheck, RuleEvaluationResult } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Evaluates a single permission rule
|
|
10
|
+
*
|
|
11
|
+
* @param rule - The rule function to evaluate
|
|
12
|
+
* @param context - The permission context
|
|
13
|
+
* @returns Promise resolving to rule result and evaluation metadata
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluateRule<TUser = any, TResource = any>(rule: PermissionRule<TUser, TResource>, context: PermissionContext<TUser, TResource>): Promise<{
|
|
16
|
+
result: boolean;
|
|
17
|
+
duration: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a string-based permission key to a rule function
|
|
22
|
+
*
|
|
23
|
+
* Strategy:
|
|
24
|
+
* 1. Check if it exists in the rules map
|
|
25
|
+
* 2. Check if it's in the permissions array (direct permission grant)
|
|
26
|
+
* 3. Check if it's in the roles array (role-based grant)
|
|
27
|
+
* 4. Otherwise deny
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveStringRule<TUser = any, TResource = any>(permissionKey: string, rulesMap: PermissionRulesMap<TUser, TResource>, context: PermissionContext<TUser, TResource>): PermissionRule<TUser, TResource>;
|
|
30
|
+
/**
|
|
31
|
+
* Evaluates a permission check (string, array, or function)
|
|
32
|
+
*
|
|
33
|
+
* @param check - The permission check to evaluate
|
|
34
|
+
* @param context - The permission context
|
|
35
|
+
* @param rulesMap - Map of named rules
|
|
36
|
+
* @param mode - Evaluation mode: 'any' (OR) or 'all' (AND)
|
|
37
|
+
* @returns Promise resolving to evaluation result and metadata
|
|
38
|
+
*/
|
|
39
|
+
export declare function evaluatePermission<TUser = any, TResource = any>(check: PermissionCheck<TUser, TResource>, context: PermissionContext<TUser, TResource>, rulesMap: PermissionRulesMap<TUser, TResource>, mode?: 'any' | 'all'): Promise<{
|
|
40
|
+
allowed: boolean;
|
|
41
|
+
ruleResults: RuleEvaluationResult[];
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a permission context from configuration values
|
|
45
|
+
*/
|
|
46
|
+
export declare function createPermissionContext<TUser = any, TResource = any>(user: TUser, resource: TResource | undefined, roles: string[], permissions: string[], flags: Record<string, boolean>): PermissionContext<TUser, TResource>;
|
|
47
|
+
//# sourceMappingURL=ruleEngine.d.ts.map
|