@cocreate/authorize 1.16.2 → 1.17.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/.github/workflows/automated.yml +25 -38
- package/CHANGELOG.md +18 -0
- package/package.json +3 -11
- package/release.config.js +12 -4
- package/src/index.js +893 -934
package/src/index.js
CHANGED
|
@@ -1,934 +1,893 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return result;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
async function checkFilter(authorized, data, apikey, unauthorize) {
|
|
900
|
-
if (data.object.$filter && data.object.$filter.query) {
|
|
901
|
-
let key;
|
|
902
|
-
if (data.object.$filter.type == "object") key = "_id";
|
|
903
|
-
else if (data.object.$filter.type == "array") key = "name";
|
|
904
|
-
if (key) {
|
|
905
|
-
for (let value of authorized[apikey]) {
|
|
906
|
-
if (value[key]) value = value[key];
|
|
907
|
-
if (!data.object.$filter.query.$or)
|
|
908
|
-
data.object.$filter.query.$or = [];
|
|
909
|
-
if (unauthorize)
|
|
910
|
-
data.object.$filter.query.$or.push({
|
|
911
|
-
[key]: { $ne: value }
|
|
912
|
-
});
|
|
913
|
-
else
|
|
914
|
-
data.object.$filter.query.$or.push({
|
|
915
|
-
[key]: value
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
if (!unauthorize) return true;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function deleteKey(data, path) {
|
|
924
|
-
if (!data || !path) return;
|
|
925
|
-
if (path.includes("._id")) path = path.replace("._id", "");
|
|
926
|
-
data = dotNotationToObject({ [path]: undefined }, data);
|
|
927
|
-
return data;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
return {
|
|
931
|
-
check
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
);
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2023 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
import { getValueFromObject, dotNotationToObject } from "@cocreate/utils";
|
|
19
|
+
import crud from "@cocreate/crud-server";
|
|
20
|
+
|
|
21
|
+
// ==========================================
|
|
22
|
+
// Module-Level Sandbox State (ESM Singleton)
|
|
23
|
+
// ==========================================
|
|
24
|
+
const organizations = {};
|
|
25
|
+
|
|
26
|
+
// ==========================================
|
|
27
|
+
// Event Listeners for Cache Invalidation
|
|
28
|
+
// ==========================================
|
|
29
|
+
if (typeof window !== "undefined") {
|
|
30
|
+
if (crud && typeof crud.listen === "function") {
|
|
31
|
+
crud.listen("object.update", function (data) {
|
|
32
|
+
updateAuthorization(data);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
crud.listen("object.delete", function (data) {
|
|
36
|
+
deleteAuthorization(data);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
if (typeof process !== "undefined") {
|
|
41
|
+
process.on("crud-event", async (data) => {
|
|
42
|
+
// Safely route deletes vs updates to maintain precise cache sync
|
|
43
|
+
if (data.method === "object.delete") {
|
|
44
|
+
deleteAuthorization(data);
|
|
45
|
+
} else {
|
|
46
|
+
updateAuthorization(data);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Updates the cached authorization for a specific key within an organization.
|
|
54
|
+
*
|
|
55
|
+
* This function checks if the `array` is `"keys"` and if an authorization object is provided.
|
|
56
|
+
* If the conditions are met, it fetches the latest authorization details using the `readAuthorization`
|
|
57
|
+
* function and updates the cache for the given `organization_id` and `key`.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} data - The data object containing:
|
|
60
|
+
* - `array`: The type of collection being updated (e.g., "keys").
|
|
61
|
+
* - `object`: The object array containing the authorization details.
|
|
62
|
+
* - `organization_id`: The ID of the organization the authorization belongs to.
|
|
63
|
+
* @returns {Promise<void>} Resolves when the update is complete.
|
|
64
|
+
*/
|
|
65
|
+
export async function updateAuthorization(data) {
|
|
66
|
+
const { array, object, organization_id } = data;
|
|
67
|
+
|
|
68
|
+
// Ensure we're working with "keys" and an authorization object is provided
|
|
69
|
+
if (array === "keys" && object) {
|
|
70
|
+
let authorization = object[0];
|
|
71
|
+
|
|
72
|
+
// Check if the authorization key exists in the cache
|
|
73
|
+
if (
|
|
74
|
+
authorization &&
|
|
75
|
+
authorization.key &&
|
|
76
|
+
organizations[organization_id] &&
|
|
77
|
+
organizations[organization_id][authorization.key]
|
|
78
|
+
) {
|
|
79
|
+
// Fetch the latest authorization details
|
|
80
|
+
let newAuthorization = await readAuthorization(
|
|
81
|
+
authorization.key,
|
|
82
|
+
data
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Update the cache with the new authorization details
|
|
86
|
+
organizations[organization_id][authorization.key] =
|
|
87
|
+
newAuthorization;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Deletes the cached authorization for a specific key within an organization.
|
|
94
|
+
*
|
|
95
|
+
* This function checks if the `array` is `"keys"` and if an authorization object is provided.
|
|
96
|
+
* If the conditions are met, it removes the cached authorization for the given
|
|
97
|
+
* `organization_id` and `key` from the `organizations` cache.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} data - The data object containing:
|
|
100
|
+
* - `array`: The type of collection being deleted (e.g., "keys").
|
|
101
|
+
* - `object`: The object array containing the authorization details.
|
|
102
|
+
* - `organization_id`: The ID of the organization the authorization belongs to.
|
|
103
|
+
* @returns {Promise<void>} Resolves when the deletion is complete.
|
|
104
|
+
*/
|
|
105
|
+
export async function deleteAuthorization(data) {
|
|
106
|
+
const { array, object, organization_id } = data;
|
|
107
|
+
|
|
108
|
+
// Ensure we're working with "keys" and an authorization object is provided
|
|
109
|
+
if (array === "keys" && object) {
|
|
110
|
+
let authorization = object[0];
|
|
111
|
+
|
|
112
|
+
// Check if the authorization key exists in the cache
|
|
113
|
+
if (
|
|
114
|
+
authorization &&
|
|
115
|
+
authorization.key &&
|
|
116
|
+
organizations[organization_id] &&
|
|
117
|
+
organizations[organization_id][authorization.key]
|
|
118
|
+
) {
|
|
119
|
+
// Remove the cached authorization for the given key
|
|
120
|
+
delete organizations[organization_id][authorization.key];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Retrieves the cached authorization for a specific key within an organization.
|
|
127
|
+
*
|
|
128
|
+
* If the authorization is not cached, this function fetches the authorization
|
|
129
|
+
* details using the `readAuthorization` function and stores it in the cache.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} key - The authorization key (e.g., user ID or API key).
|
|
132
|
+
* @param {object} data - The data object containing:
|
|
133
|
+
* - `organization_id`: The ID of the organization the authorization belongs to.
|
|
134
|
+
* @returns {Promise<object>} A promise that resolves to the authorization details.
|
|
135
|
+
*/
|
|
136
|
+
export async function getAuthorization(key, data) {
|
|
137
|
+
let organization_id = data.organization_id;
|
|
138
|
+
|
|
139
|
+
// Initialize the cache for the organization if it doesn't exist
|
|
140
|
+
if (!organizations[organization_id])
|
|
141
|
+
organizations[organization_id] = {};
|
|
142
|
+
|
|
143
|
+
// If the authorization for the key is not cached, fetch and cache it
|
|
144
|
+
if (!organizations[organization_id][key])
|
|
145
|
+
organizations[organization_id][key] = readAuthorization(
|
|
146
|
+
key,
|
|
147
|
+
data
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Await the authorization (handles the promise stored during the initial fetch)
|
|
151
|
+
organizations[organization_id][key] = await organizations[
|
|
152
|
+
organization_id
|
|
153
|
+
][key];
|
|
154
|
+
|
|
155
|
+
return organizations[organization_id][key];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reads authorization details for a given key (user ID or API key) and organization.
|
|
160
|
+
*
|
|
161
|
+
* This function constructs a request to retrieve authorization details from the database.
|
|
162
|
+
* If the `key` is provided, it queries for the specific authorization key.
|
|
163
|
+
* If no `key` is provided, it queries for the default authorization settings.
|
|
164
|
+
*
|
|
165
|
+
* Additionally, it resolves roles associated with the authorization and merges role permissions
|
|
166
|
+
* into the final authorization object.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} key - The authorization key (e.g., user ID or API key).
|
|
169
|
+
* @param {object} data - The data object containing request details, such as the `organization_id` and `host`.
|
|
170
|
+
* @returns {Promise<object>} A promise that resolves to the authorization object or an error message.
|
|
171
|
+
*/
|
|
172
|
+
export async function readAuthorization(key, data) {
|
|
173
|
+
try {
|
|
174
|
+
// Extract and validate the organization_id
|
|
175
|
+
let organization_id = data.organization_id;
|
|
176
|
+
if (!organization_id)
|
|
177
|
+
return { error: "An organization_id is required" };
|
|
178
|
+
|
|
179
|
+
// Construct the initial request for authorization data
|
|
180
|
+
let request = {
|
|
181
|
+
method: "object.read", // CRUD method to read the object
|
|
182
|
+
host: data.host, // Host associated with the request
|
|
183
|
+
database: organization_id, // Database scoped to the organization
|
|
184
|
+
array: "keys", // Collection or array to query
|
|
185
|
+
organization_id, // Organization context for the request
|
|
186
|
+
$filter: {
|
|
187
|
+
query: {}, // Query filters to apply
|
|
188
|
+
limit: 2 // Limit to 2 results to fetch both default and specific key if needed
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Always fetch the default authorization and, if provided, the specific key
|
|
193
|
+
request.$filter.query.$or = [{ default: true }];
|
|
194
|
+
if (key) request.$filter.query.$or.push({ key: key });
|
|
195
|
+
|
|
196
|
+
// Send the request to the CRUD layer
|
|
197
|
+
let authRes = await crud.send(request);
|
|
198
|
+
|
|
199
|
+
// If authorization data exists, merge default + key (key overrides default)
|
|
200
|
+
if (authRes && authRes.object && authRes.object.length) {
|
|
201
|
+
const objs = authRes.object;
|
|
202
|
+
const defaultDoc = objs.find((o) => o && o.default) || {};
|
|
203
|
+
const keyDoc = key ? objs.find((o) => o && o.key === key) : undefined;
|
|
204
|
+
|
|
205
|
+
// Expand dot-notation fields and merge: defaults then key-specific (key overrides)
|
|
206
|
+
const expandedDefault = dotNotationToObject({}, defaultDoc || {});
|
|
207
|
+
const expandedKey = keyDoc ? dotNotationToObject({}, keyDoc) : {};
|
|
208
|
+
let authorization = dotNotationToObject(expandedDefault, expandedKey);
|
|
209
|
+
|
|
210
|
+
// Merge roles uniquely (default roles + key roles)
|
|
211
|
+
const defaultRoles = defaultDoc.roles || [];
|
|
212
|
+
const keyRoles = (keyDoc && keyDoc.roles) || [];
|
|
213
|
+
authorization.roles = Array.from(new Set([...defaultRoles, ...keyRoles]));
|
|
214
|
+
|
|
215
|
+
// Ensure the 'arrays' field exists to prevent undefined references
|
|
216
|
+
if (!authorization.arrays) authorization.arrays = {};
|
|
217
|
+
|
|
218
|
+
// Process roles associated with the authorization (if any)
|
|
219
|
+
if (authorization && authorization.roles && authorization.roles.length) {
|
|
220
|
+
const role_ids = [];
|
|
221
|
+
authorization.roles.forEach((_id) => {
|
|
222
|
+
if (_id) role_ids.push({ _id });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Prepare a new request to fetch role details
|
|
226
|
+
delete request.$filter;
|
|
227
|
+
delete request.isFilter;
|
|
228
|
+
|
|
229
|
+
request.object = role_ids;
|
|
230
|
+
|
|
231
|
+
// Retrieve and merge role permissions into the authorization object
|
|
232
|
+
let roles = await crud.send(request);
|
|
233
|
+
roles = roles.object;
|
|
234
|
+
|
|
235
|
+
for (let role of roles) {
|
|
236
|
+
authorization = dotNotationToObject(authorization, role);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return authorization; // Return the final merged authorization object
|
|
241
|
+
} else {
|
|
242
|
+
// Return an empty object if no authorization is found
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
// Handle and log any errors during the process
|
|
247
|
+
console.log("authorization Error", error);
|
|
248
|
+
return { error };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Validates the authorization of a user or an API key to perform an action.
|
|
254
|
+
*
|
|
255
|
+
* The function first checks authorization using the provided `user_id`.
|
|
256
|
+
* If that fails or returns an error, it checks the authorization using the `apikey`
|
|
257
|
+
* present in the `data` object.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} data - The data object containing request details, such as the `apikey` and `organization_id`.
|
|
260
|
+
* @param {string} user_id - The user ID for authorization validation.
|
|
261
|
+
* @returns {Promise<object|boolean>} A promise that resolves to the authorization result:
|
|
262
|
+
* - `false` if unauthorized.
|
|
263
|
+
* - An object with authorization details if authorized.
|
|
264
|
+
*/
|
|
265
|
+
export async function check(data, user_id) {
|
|
266
|
+
let authorization = false;
|
|
267
|
+
|
|
268
|
+
// Attempt to check authorization with the user ID if provided
|
|
269
|
+
if (user_id) {
|
|
270
|
+
authorization = await checkAuthorization({
|
|
271
|
+
key: user_id,
|
|
272
|
+
data
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Fallback to checking authorization with the API key if user ID check fails
|
|
277
|
+
if (!authorization || authorization.error) {
|
|
278
|
+
authorization = await checkAuthorization({
|
|
279
|
+
key: data.apikey,
|
|
280
|
+
data
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return authorization;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Checks whether a given key (user ID or API key) is authorized to perform the specified action.
|
|
289
|
+
*
|
|
290
|
+
* The function validates the presence of required fields in the `data` object (`organization_id`, `method`),
|
|
291
|
+
* fetches authorization details, and performs checks such as:
|
|
292
|
+
* - Organization ID matching.
|
|
293
|
+
* - Host-level restrictions.
|
|
294
|
+
* - Admin privileges.
|
|
295
|
+
* - Defined actions and their permissions.
|
|
296
|
+
*
|
|
297
|
+
* @param {object} params - The parameters for the function.
|
|
298
|
+
* @param {string} params.key - The key used for authorization (user ID or API key).
|
|
299
|
+
* @param {object} params.data - The data object containing details like `organization_id`, `host`, and `method`.
|
|
300
|
+
* @returns {Promise<object|boolean>} A promise that resolves to:
|
|
301
|
+
* - An object with an error message if validation fails.
|
|
302
|
+
* - `false` if authorization checks fail.
|
|
303
|
+
* - `true` or an object with sanitized `data` if authorized.
|
|
304
|
+
*/
|
|
305
|
+
export async function checkAuthorization({ key, data }) {
|
|
306
|
+
// Validate required fields
|
|
307
|
+
if (!data.organization_id)
|
|
308
|
+
return { error: "organization_id is required" };
|
|
309
|
+
if (!data.method) return { error: "method is required" };
|
|
310
|
+
|
|
311
|
+
// Fetch authorization details using the provided key
|
|
312
|
+
let authorized = await getAuthorization(key, data);
|
|
313
|
+
|
|
314
|
+
// If no authorization details or an error is returned, propagate the error or deny access
|
|
315
|
+
if (!authorized || authorized.error) return authorized;
|
|
316
|
+
|
|
317
|
+
// Ensure the organization ID matches
|
|
318
|
+
if (authorized.organization_id !== data.organization_id)
|
|
319
|
+
return false;
|
|
320
|
+
|
|
321
|
+
// Validate host restrictions if specified
|
|
322
|
+
if (authorized.host && authorized.host.length) {
|
|
323
|
+
if (
|
|
324
|
+
!authorized.host ||
|
|
325
|
+
(!authorized.host.includes(data.host) &&
|
|
326
|
+
!authorized.host.includes("*"))
|
|
327
|
+
)
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Allow access if the user has admin privileges
|
|
332
|
+
if (authorized.admin === "true" || authorized.admin === true)
|
|
333
|
+
return true;
|
|
334
|
+
|
|
335
|
+
// Ensure actions are defined for the authorization
|
|
336
|
+
if (!authorized.actions)
|
|
337
|
+
return {
|
|
338
|
+
error: "Authorization does not have any actions defined"
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Check if the requested method is allowed under the defined actions
|
|
342
|
+
let status = await checkMethod(
|
|
343
|
+
data,
|
|
344
|
+
authorized.actions,
|
|
345
|
+
data.method
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// If the method check fails, deny access
|
|
349
|
+
if (!status) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Return the sanitized and authorized data if all checks pass
|
|
354
|
+
return { authorized: data };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Validates whether a method is permitted based on the authorization rules.
|
|
359
|
+
*
|
|
360
|
+
* This function checks for an exact match of the `method` in the `authorized` actions.
|
|
361
|
+
* If no exact match is found, it checks progressively for partial matches using dot notation (e.g., "read.user").
|
|
362
|
+
*
|
|
363
|
+
* @param {object} data - The data object containing request details.
|
|
364
|
+
* @param {object} authorized - The authorization rules object mapping methods to permissions.
|
|
365
|
+
* @param {string} method - The method to validate (e.g., "read.user").
|
|
366
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the method is authorized, `false` otherwise.
|
|
367
|
+
*/
|
|
368
|
+
export async function checkMethod(data, authorized, method) {
|
|
369
|
+
// Check for an exact match of the method in the authorization rules
|
|
370
|
+
if (authorized[method]) {
|
|
371
|
+
return await checkMethodMatch(data, authorized[method], method);
|
|
372
|
+
}
|
|
373
|
+
// Check for partial matches using dot notation (e.g., "read.user")
|
|
374
|
+
else if (method.includes(".")) {
|
|
375
|
+
let match = "";
|
|
376
|
+
let splitMethod = method.split(".");
|
|
377
|
+
|
|
378
|
+
for (let i = 0; i < splitMethod.length; i++) {
|
|
379
|
+
if (!match) match = splitMethod[i];
|
|
380
|
+
else match += "." + splitMethod[i];
|
|
381
|
+
|
|
382
|
+
// If a partial match is found, validate it
|
|
383
|
+
if (authorized[match]) {
|
|
384
|
+
return await checkMethodMatch(
|
|
385
|
+
data,
|
|
386
|
+
authorized[match],
|
|
387
|
+
match
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
// If the last segment has no match, deny access
|
|
391
|
+
else if (i === splitMethod.length - 1) return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Deny access if no match is found
|
|
395
|
+
else return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Checks whether a given method matches the authorized permissions and
|
|
400
|
+
* optionally sanitizes the provided data based on inclusion and exclusion rules.
|
|
401
|
+
*
|
|
402
|
+
* This function supports various types of authorization configurations, including:
|
|
403
|
+
* - Booleans: Directly allow (`true`) or deny (`false`).
|
|
404
|
+
* - Strings: Match the authorization string with the method or default to allow.
|
|
405
|
+
* - Numbers: Treat any non-zero number as `true` (authorized).
|
|
406
|
+
* - Arrays: Recursively check multiple authorization rules.
|
|
407
|
+
* - Objects: Evaluate nested authorization rules and apply inclusion/exclusion logic.
|
|
408
|
+
*
|
|
409
|
+
* The function also aggregates all inclusion and exclusion rules before sanitizing
|
|
410
|
+
* the data, ensuring consistent prioritization logic is applied holistically.
|
|
411
|
+
*
|
|
412
|
+
* @param {object} data - The data object containing the method and other fields to process.
|
|
413
|
+
* @param {boolean | string | number | object | Array} authorized - The authorization rules.
|
|
414
|
+
* @param {string} match - The method string to match against the authorization rules.
|
|
415
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the method is authorized,
|
|
416
|
+
* `false` otherwise.
|
|
417
|
+
*/
|
|
418
|
+
export async function checkMethodMatch(data, authorized, match) {
|
|
419
|
+
if (typeof authorized === "boolean") {
|
|
420
|
+
return authorized;
|
|
421
|
+
} else if (typeof authorized === "string") {
|
|
422
|
+
if (authorized === "false") return false;
|
|
423
|
+
else if (authorized === "true") return true;
|
|
424
|
+
else if (authorized === match) return true;
|
|
425
|
+
else return true;
|
|
426
|
+
} else if (typeof authorized === "number") {
|
|
427
|
+
return !!authorized;
|
|
428
|
+
} else {
|
|
429
|
+
let status = false;
|
|
430
|
+
let newmatch = data.method.replace(match + ".", "");
|
|
431
|
+
|
|
432
|
+
// Aggregate raw keys
|
|
433
|
+
let rawKeys = [];
|
|
434
|
+
|
|
435
|
+
if (Array.isArray(authorized)) {
|
|
436
|
+
for (let i = 0; i < authorized.length; i++) {
|
|
437
|
+
status = await checkMethodMatch(
|
|
438
|
+
data,
|
|
439
|
+
authorized[i],
|
|
440
|
+
newmatch
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
} else if (typeof authorized === "object") {
|
|
444
|
+
for (const key of Object.keys(authorized)) {
|
|
445
|
+
if (key.includes("$")) {
|
|
446
|
+
if (
|
|
447
|
+
[
|
|
448
|
+
"$storage",
|
|
449
|
+
"$database",
|
|
450
|
+
"$array",
|
|
451
|
+
"$index"
|
|
452
|
+
].includes(key)
|
|
453
|
+
) {
|
|
454
|
+
let opStatus = await checkMethodOperators(
|
|
455
|
+
data,
|
|
456
|
+
key,
|
|
457
|
+
authorized[key]
|
|
458
|
+
);
|
|
459
|
+
if (opStatus === true || opStatus === false)
|
|
460
|
+
status = opStatus;
|
|
461
|
+
} else if (key === "$keys") {
|
|
462
|
+
let type = data.method.split(".")[0];
|
|
463
|
+
if (data[type]) {
|
|
464
|
+
rawKeys.push(authorized[key]); // Collect raw authorization keys
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
let isFilter = applyFilter(
|
|
468
|
+
data,
|
|
469
|
+
authorized[key],
|
|
470
|
+
key
|
|
471
|
+
);
|
|
472
|
+
console.log("isFilter", isFilter);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Parse all raw keys together to get inclusion/exclusion
|
|
479
|
+
let { inclusion, exclusion } = parsePermissions(rawKeys);
|
|
480
|
+
|
|
481
|
+
// Apply sanitization if there are inclusion or exclusion rules
|
|
482
|
+
if (inclusion || exclusion) {
|
|
483
|
+
let type = data.method.split(".")[0]; // Extract type from method
|
|
484
|
+
if (data[type]) {
|
|
485
|
+
data[type] = sanitizeData(
|
|
486
|
+
data[type],
|
|
487
|
+
inclusion,
|
|
488
|
+
exclusion
|
|
489
|
+
);
|
|
490
|
+
status = true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (newmatch) {
|
|
495
|
+
if (!status && authorized[newmatch]) {
|
|
496
|
+
status = await checkMethodMatch(
|
|
497
|
+
data,
|
|
498
|
+
authorized[newmatch],
|
|
499
|
+
newmatch
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (!status && authorized["*"]) {
|
|
503
|
+
status = await checkMethodMatch(
|
|
504
|
+
data,
|
|
505
|
+
authorized["*"],
|
|
506
|
+
newmatch
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return status;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Checks method operators to validate authorization based on dynamic data types and keys.
|
|
517
|
+
*
|
|
518
|
+
* @param {Object} data - The input data object containing various properties.
|
|
519
|
+
* @param {string} key - A string key to determine the type of operation,
|
|
520
|
+
* starting with `$` for dynamic type-based checks.
|
|
521
|
+
* @param {string} authorization - The authorization identifier, which could be a static value
|
|
522
|
+
* or a dynamic reference like `$user_id`.
|
|
523
|
+
*
|
|
524
|
+
* @returns {boolean|undefined} - Returns `true` if authorized, `false` if unauthorized,
|
|
525
|
+
* or `undefined` if no validation applies.
|
|
526
|
+
*/
|
|
527
|
+
export async function checkMethodOperators(data, key, authorization) {
|
|
528
|
+
try {
|
|
529
|
+
// Adjust authorization dynamically if `$user_id` is provided and user info exists in the socket
|
|
530
|
+
if (authorization === "$user_id" && data.socket) {
|
|
531
|
+
authorization = data.socket.user_id || data.user_id;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Check if the key starts with `$`, indicating a dynamic type-based operation
|
|
535
|
+
if (key.startsWith("$")) {
|
|
536
|
+
let type = key.substring(1); // Extract the type by removing `$`
|
|
537
|
+
|
|
538
|
+
// If the specified type does not exist in data, return undefined
|
|
539
|
+
if (!data[type]) {
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Handle cases where the type is a string
|
|
544
|
+
if (typeof data[type] === "string") {
|
|
545
|
+
return checkAuthorizationData(
|
|
546
|
+
data,
|
|
547
|
+
authorization,
|
|
548
|
+
data[type]
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
// Handle cases where the type is an array
|
|
552
|
+
else if (Array.isArray(data[type])) {
|
|
553
|
+
if (!data[type].length) return undefined; // Return undefined for empty arrays
|
|
554
|
+
|
|
555
|
+
// ToDo: Current strategy ensures all items must match; adjust as needed
|
|
556
|
+
let allAuthorized = true;
|
|
557
|
+
for (let i = 0; i < data[type].length; i++) {
|
|
558
|
+
const itemData =
|
|
559
|
+
typeof data[type][i] === "object"
|
|
560
|
+
? data[type][i].name // Extract name if the item is an object
|
|
561
|
+
: data[type][i];
|
|
562
|
+
const authResult = checkAuthorizationData(
|
|
563
|
+
data,
|
|
564
|
+
authorization,
|
|
565
|
+
itemData
|
|
566
|
+
);
|
|
567
|
+
if (authResult === false) {
|
|
568
|
+
return false; // Return false if any item fails validation
|
|
569
|
+
} else if (authResult === undefined) {
|
|
570
|
+
allAuthorized = undefined; // Mark as undefined if any validation is inconclusive
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return allAuthorized;
|
|
574
|
+
}
|
|
575
|
+
// Handle cases where the type is an object
|
|
576
|
+
else if (typeof data[type] === "object") {
|
|
577
|
+
return checkAuthorizationData(
|
|
578
|
+
data,
|
|
579
|
+
authorization,
|
|
580
|
+
data[type].name
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Return undefined if no conditions match
|
|
586
|
+
return undefined;
|
|
587
|
+
} catch (e) {
|
|
588
|
+
// Log the error and return undefined in case of an exception
|
|
589
|
+
console.log(e);
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Checks if the provided key is authorized based on the authorization rules.
|
|
596
|
+
*
|
|
597
|
+
* @param {Object} data - The data object to be used in filtering.
|
|
598
|
+
* @param {string|Array|Object} authorization - The authorization rules, which can be a string, array, or object.
|
|
599
|
+
* @param {string} key - The key to be checked against the authorization rules.
|
|
600
|
+
* @returns {boolean|undefined} - Returns true if authorized, false if explicitly unauthorized, and undefined if no rule matches.
|
|
601
|
+
*/
|
|
602
|
+
export function checkAuthorizationData(data, authorization, key) {
|
|
603
|
+
// If the authorization is a string, check if it matches the key.
|
|
604
|
+
if (typeof authorization === "string") {
|
|
605
|
+
if (key === authorization)
|
|
606
|
+
return true; // Key matches the authorization string.
|
|
607
|
+
else return undefined; // No match, return undefined.
|
|
608
|
+
}
|
|
609
|
+
// If the authorization is an array, check if the key is included in the array.
|
|
610
|
+
else if (Array.isArray(authorization)) {
|
|
611
|
+
if (authorization.includes(key))
|
|
612
|
+
return true; // Key is included in the array.
|
|
613
|
+
else return undefined; // Key not found, return undefined.
|
|
614
|
+
}
|
|
615
|
+
// If the authorization is an object, handle various object-based rules.
|
|
616
|
+
else if (typeof authorization === "object") {
|
|
617
|
+
// If the authorization[key] is an object, apply filters recursively.
|
|
618
|
+
if (typeof authorization[key] === "object") {
|
|
619
|
+
for (const queryKey of Object.keys(authorization[key])) {
|
|
620
|
+
return applyFilter(data, authorization[key], queryKey); // Apply the filter logic.
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// If authorization[key] is explicitly false or "false", return false.
|
|
624
|
+
else if (
|
|
625
|
+
authorization[key] === false ||
|
|
626
|
+
authorization[key] === "false"
|
|
627
|
+
)
|
|
628
|
+
return false;
|
|
629
|
+
// If authorization[key] exists and is truthy, return true.
|
|
630
|
+
else if (authorization[key]) return true;
|
|
631
|
+
// If none of the above conditions match, return undefined.
|
|
632
|
+
else return undefined;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export function applyFilter(data, authorization, authorizationKey) {
|
|
637
|
+
let keyParts = authorizationKey.split(".");
|
|
638
|
+
let operator = keyParts.pop();
|
|
639
|
+
let key = keyParts.join(".");
|
|
640
|
+
if (
|
|
641
|
+
[
|
|
642
|
+
"$eq",
|
|
643
|
+
"$ne",
|
|
644
|
+
"$lt",
|
|
645
|
+
"$lte",
|
|
646
|
+
"$gt",
|
|
647
|
+
"$gte",
|
|
648
|
+
"$in",
|
|
649
|
+
"$nin",
|
|
650
|
+
"$or",
|
|
651
|
+
"$and",
|
|
652
|
+
"$not",
|
|
653
|
+
"$nor",
|
|
654
|
+
"$exists",
|
|
655
|
+
"$type",
|
|
656
|
+
"$mod",
|
|
657
|
+
"$regex",
|
|
658
|
+
"$text",
|
|
659
|
+
"$where",
|
|
660
|
+
"$all",
|
|
661
|
+
"$elemMatch",
|
|
662
|
+
"$size"
|
|
663
|
+
].includes(operator)
|
|
664
|
+
) {
|
|
665
|
+
if (!data.$filter) data.$filter = { query: {} };
|
|
666
|
+
else if (!data.$filter.query) data.$filter.query = {};
|
|
667
|
+
|
|
668
|
+
if (
|
|
669
|
+
authorization[authorizationKey] === "$user_id" &&
|
|
670
|
+
data.socket
|
|
671
|
+
) {
|
|
672
|
+
authorization[authorizationKey] =
|
|
673
|
+
data.socket.user_id || data.user_id;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (typeof authorization === "string")
|
|
677
|
+
data.$filter.query[key] = {
|
|
678
|
+
[operator]: authorization
|
|
679
|
+
};
|
|
680
|
+
else
|
|
681
|
+
data.$filter.query[key] = {
|
|
682
|
+
[operator]: authorization[authorizationKey]
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Parses permissions into inclusion and exclusion arrays.
|
|
691
|
+
*
|
|
692
|
+
* @param {string | object | Array<string | object>} authorization - The authorization to parse.
|
|
693
|
+
* @returns {object} An object containing 'inclusion' and 'exclusion' arrays.
|
|
694
|
+
*/
|
|
695
|
+
export function parsePermissions(authorization) {
|
|
696
|
+
// Initialize local arrays for inclusion and exclusion
|
|
697
|
+
const inclusion = [];
|
|
698
|
+
const exclusion = [];
|
|
699
|
+
|
|
700
|
+
// Helper function to process a single permission entry
|
|
701
|
+
function processPermission(entry) {
|
|
702
|
+
if (typeof entry === "string") {
|
|
703
|
+
// Treat string as inclusion
|
|
704
|
+
inclusion.push(entry);
|
|
705
|
+
} else if (typeof entry === "object" && entry !== null) {
|
|
706
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
707
|
+
if (value === true) {
|
|
708
|
+
inclusion.push(key);
|
|
709
|
+
} else if (value === false) {
|
|
710
|
+
exclusion.push(key);
|
|
711
|
+
}
|
|
712
|
+
// Ignore entries where value is neither true nor false
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Ignore entries that are neither string nor object
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Normalize the input into an array of permission entries
|
|
719
|
+
let permissionEntries = [];
|
|
720
|
+
|
|
721
|
+
if (Array.isArray(authorization)) {
|
|
722
|
+
permissionEntries = authorization;
|
|
723
|
+
} else if (
|
|
724
|
+
typeof authorization === "string" ||
|
|
725
|
+
(typeof authorization === "object" && authorization !== null)
|
|
726
|
+
) {
|
|
727
|
+
permissionEntries = [authorization];
|
|
728
|
+
}
|
|
729
|
+
// If authorization is undefined or null, treat as empty
|
|
730
|
+
|
|
731
|
+
// Process each permission entry
|
|
732
|
+
permissionEntries.forEach(processPermission);
|
|
733
|
+
|
|
734
|
+
// Apply prioritization logic
|
|
735
|
+
if (inclusion.length > 0) {
|
|
736
|
+
// If any inclusion exists, disregard exclusions
|
|
737
|
+
return { inclusion, exclusion: null };
|
|
738
|
+
} else if (exclusion.length > 0) {
|
|
739
|
+
// If only exclusions exist, disregard inclusions
|
|
740
|
+
return { inclusion: null, exclusion };
|
|
741
|
+
} else {
|
|
742
|
+
// If neither exists, set both to null
|
|
743
|
+
return { inclusion: null, exclusion: null };
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Sanitizes data based on parsed permissions.
|
|
749
|
+
*
|
|
750
|
+
* @param {object | Array} data - The data object or array to sanitize.
|
|
751
|
+
* @param {Array} inclusion - The parsed inclusion array.
|
|
752
|
+
* @param {Array} exclusion - The parsed exclusion array.
|
|
753
|
+
* @returns {object | Array} The sanitized data object or array.
|
|
754
|
+
*/
|
|
755
|
+
export function sanitizeData(data, inclusion, exclusion) {
|
|
756
|
+
// If both inclusion and exclusion are null, return the data as is
|
|
757
|
+
if (inclusion === null && exclusion === null) {
|
|
758
|
+
return data;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (Array.isArray(data)) {
|
|
762
|
+
return data.map((item) =>
|
|
763
|
+
sanitizeData(item, inclusion, exclusion)
|
|
764
|
+
);
|
|
765
|
+
} else if (typeof data === "object" && data !== null) {
|
|
766
|
+
let sanitized = {};
|
|
767
|
+
|
|
768
|
+
if (inclusion) {
|
|
769
|
+
// Include only specified fields (supports nested fields)
|
|
770
|
+
for (let i = 0; i < inclusion.length; i++) {
|
|
771
|
+
let inclusionData = generateDotNotation(
|
|
772
|
+
data,
|
|
773
|
+
inclusion[i]
|
|
774
|
+
);
|
|
775
|
+
sanitized = { ...sanitized, ...inclusionData };
|
|
776
|
+
}
|
|
777
|
+
return dotNotationToObject(sanitized);
|
|
778
|
+
} else if (exclusion) {
|
|
779
|
+
for (let i = 0; i < exclusion.length; i++) {
|
|
780
|
+
let exclusionData = generateDotNotation(
|
|
781
|
+
data,
|
|
782
|
+
exclusion[i],
|
|
783
|
+
true
|
|
784
|
+
);
|
|
785
|
+
sanitized = { ...sanitized, ...exclusionData };
|
|
786
|
+
}
|
|
787
|
+
return dotNotationToObject(sanitized, data);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return data;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Generates an object with dot notation keys from the given data and path.
|
|
796
|
+
*/
|
|
797
|
+
export function generateDotNotation(data, path, isEclusion) {
|
|
798
|
+
if (typeof path !== "string")
|
|
799
|
+
throw new Error("Path must be a string");
|
|
800
|
+
|
|
801
|
+
let result = {};
|
|
802
|
+
|
|
803
|
+
function processKeys(dataItem, keys, prefix = "", result) {
|
|
804
|
+
if (dataItem === undefined) return;
|
|
805
|
+
|
|
806
|
+
if (!keys.length) {
|
|
807
|
+
if (isEclusion) {
|
|
808
|
+
result[prefix] = undefined;
|
|
809
|
+
} else {
|
|
810
|
+
result[prefix] = dataItem;
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
let key = keys.shift();
|
|
816
|
+
let isArray = key.endsWith("[]");
|
|
817
|
+
if (isArray) key = key.slice(0, -2); // Remove [] from key
|
|
818
|
+
|
|
819
|
+
// Construct prefix
|
|
820
|
+
prefix = prefix ? `${prefix}.${key}` : key;
|
|
821
|
+
|
|
822
|
+
const currentData = getValueFromObject(dataItem, key); // Using your existing function
|
|
823
|
+
|
|
824
|
+
if (isArray && Array.isArray(currentData)) {
|
|
825
|
+
currentData.forEach((item, index) => {
|
|
826
|
+
const newPrefix = `${prefix}[${index}]`;
|
|
827
|
+
processKeys(item, [...keys], newPrefix, result);
|
|
828
|
+
});
|
|
829
|
+
} else if (!isArray && currentData !== undefined) {
|
|
830
|
+
processKeys(currentData, keys, prefix, result);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (data && path.trim()) {
|
|
835
|
+
const keys = path.split(".");
|
|
836
|
+
processKeys(data, keys, "", result);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export async function checkFilter(authorized, data, apikey, unauthorize) {
|
|
843
|
+
if (data.object.$filter && data.object.$filter.query) {
|
|
844
|
+
let key;
|
|
845
|
+
if (data.object.$filter.type == "object") key = "_id";
|
|
846
|
+
else if (data.object.$filter.type == "array") key = "name";
|
|
847
|
+
if (key) {
|
|
848
|
+
for (let value of authorized[apikey]) {
|
|
849
|
+
if (value[key]) value = value[key];
|
|
850
|
+
if (!data.object.$filter.query.$or)
|
|
851
|
+
data.object.$filter.query.$or = [];
|
|
852
|
+
if (unauthorize)
|
|
853
|
+
data.object.$filter.query.$or.push({
|
|
854
|
+
[key]: { $ne: value }
|
|
855
|
+
});
|
|
856
|
+
else
|
|
857
|
+
data.object.$filter.query.$or.push({
|
|
858
|
+
[key]: value
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (!unauthorize) return true;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export function deleteKey(data, path) {
|
|
867
|
+
if (!data || !path) return;
|
|
868
|
+
if (path.includes("._id")) path = path.replace("._id", "");
|
|
869
|
+
data = dotNotationToObject({ [path]: undefined }, data);
|
|
870
|
+
return data;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ==========================================
|
|
874
|
+
// Bound API Assembly Object
|
|
875
|
+
// ==========================================
|
|
876
|
+
export default {
|
|
877
|
+
check,
|
|
878
|
+
updateAuthorization,
|
|
879
|
+
deleteAuthorization,
|
|
880
|
+
getAuthorization,
|
|
881
|
+
readAuthorization,
|
|
882
|
+
checkAuthorization,
|
|
883
|
+
checkMethod,
|
|
884
|
+
checkMethodMatch,
|
|
885
|
+
checkMethodOperators,
|
|
886
|
+
checkAuthorizationData,
|
|
887
|
+
applyFilter,
|
|
888
|
+
parsePermissions,
|
|
889
|
+
sanitizeData,
|
|
890
|
+
generateDotNotation,
|
|
891
|
+
checkFilter,
|
|
892
|
+
deleteKey
|
|
893
|
+
};
|