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