@edgedev/firebase 1.6.3 → 1.7.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.
Files changed (3) hide show
  1. package/README.md +465 -20
  2. package/edgeFirebase.ts +366 -232
  3. package/package.json +1 -1
package/edgeFirebase.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { initializeApp } from "firebase/app";
2
- import { reactive, computed } from "vue";
2
+ import { reactive } from "vue";
3
3
  import {
4
4
  getFirestore,
5
5
  collection,
@@ -95,27 +95,44 @@ interface UserDataObject {
95
95
  meta: object;
96
96
  roles: role[];
97
97
  specialPermissions: specialPermission[];
98
+ stagedDocId: string;
98
99
  }
99
100
 
100
101
  interface newUser {
101
- email: string;
102
102
  roles: role[];
103
103
  specialPermissions: specialPermission[];
104
104
  meta: object;
105
- }
106
-
107
-
108
- interface userMeta extends newUser {
109
- docId: string;
110
- userId: string;
105
+ isTemplate?: boolean;
106
+ subCreate?: {
107
+ rootPath: string, // This must be a collection path (odd number of segments) since a document will be created and assigned to ther user here.
108
+ role: string,
109
+ dynamicDocumentField: string, // This is the field in the document that will be set by the value of "dynamicDocumentFieldValue" passed during registration, like "name"
110
+ documentStructure: {
111
+ [key: string]: any
112
+ }
113
+ };
111
114
  }
112
115
 
113
116
  interface userRegister {
114
117
  email: string;
115
118
  password: string;
116
119
  meta: object;
120
+ registrationCode: string;
121
+ dynamicDocumentFieldValue?: string;
117
122
  }
118
123
 
124
+
125
+ interface RuleCheck {
126
+ permissionType: string;
127
+ permissionCheckPath: string;
128
+ fullPath: string;
129
+ action: string;
130
+ }
131
+
132
+ // interface RuleCheck {
133
+ // [key: string]: RuleCheckItem | string;
134
+ // }
135
+
119
136
  interface Credentials {
120
137
  email: string;
121
138
  password: string;
@@ -188,14 +205,9 @@ export const EdgeFirebase = class {
188
205
  public auth = null;
189
206
  public db = null;
190
207
 
191
- private initUserMetaPermissions = async (): Promise<void> => {
192
- updateDoc(doc(this.db, "users", this.user.email), {
193
- userId: this.user.uid
194
- });
208
+ private initUserMetaPermissions = async (docSnap): Promise<void> => {
195
209
  this.user.meta = {};
196
- const docRef = doc(this.db, "users", this.user.email);
197
- const docSnap = await getDoc(docRef);
198
- if (docSnap) {
210
+ if (docSnap.exists()) {
199
211
  this.user.meta = docSnap.data().meta;
200
212
  const roles: role[] = [];
201
213
  if (docSnap.data().roles) {
@@ -219,44 +231,35 @@ export const EdgeFirebase = class {
219
231
  }
220
232
  }
221
233
  this.user.specialPermissions = specialPermissions;
234
+ this.user.stagedDocId = docSnap.data().stagedDocId;
222
235
  }
223
236
  this.stopSnapshot('userMeta')
224
237
  const metaUnsubscribe = onSnapshot(
225
- doc(this.db, "users", this.user.email),
238
+ doc(this.db, "users", this.user.uid),
226
239
  (doc) => {
227
- if (!doc.exists()) {
228
- this.setUser({
229
- email: this.user.email,
230
- roles: [],
231
- specialPermissions: [],
232
- meta: {},
233
- });
234
- this.user.meta = {};
235
- } else {
236
- this.user.meta = doc.data().meta;
237
- const roles: role[] = [];
238
- if (doc.data().roles) {
239
- for (const collectionPath in doc.data().roles) {
240
- roles.push({
241
- collectionPath,
242
- role: doc.data().roles[collectionPath].role
243
- });
244
- }
240
+ this.user.meta = doc.data().meta;
241
+ const roles: role[] = [];
242
+ if (doc.data().roles) {
243
+ for (const collectionPath in doc.data().roles) {
244
+ roles.push({
245
+ collectionPath,
246
+ role: doc.data().roles[collectionPath].role
247
+ });
245
248
  }
246
- this.user.roles = roles;
247
-
248
- const specialPermissions: specialPermission[] = [];
249
- if (doc.data().specialPermissions) {
250
- for (const collectionPath in doc.data().specialPermissions) {
251
- specialPermissions.push({
252
- collectionPath,
253
- permissions:
254
- doc.data().specialPermissions[collectionPath].permissions
255
- });
256
- }
249
+ }
250
+ this.user.roles = roles;
251
+
252
+ const specialPermissions: specialPermission[] = [];
253
+ if (doc.data().specialPermissions) {
254
+ for (const collectionPath in doc.data().specialPermissions) {
255
+ specialPermissions.push({
256
+ collectionPath,
257
+ permissions:
258
+ doc.data().specialPermissions[collectionPath].permissions
259
+ });
257
260
  }
258
- this.user.specialPermissions = specialPermissions;
259
261
  }
262
+ this.user.specialPermissions = specialPermissions;
260
263
  }
261
264
  );
262
265
  this.unsubscibe.userMeta = metaUnsubscribe;
@@ -323,12 +326,25 @@ export const EdgeFirebase = class {
323
326
 
324
327
 
325
328
 
326
- private startUserMetaSync = async (): Promise<void> => {
329
+ private startUserMetaSync = async (docSnap): Promise<void> => {
327
330
  await this.startCollectionPermissionsSync()
328
- await this.initUserMetaPermissions();
331
+ await this.initUserMetaPermissions(docSnap);
329
332
  this.user.loggedIn = true;
330
333
  };
331
334
 
335
+ private waitForUser = async(): Promise<void> => {
336
+ //On registration may take a second for user to be created
337
+ const docRef = doc(this.db, "users", this.user.uid);
338
+ const docSnap = await getDoc(docRef);
339
+ if (docSnap.exists()) {
340
+ this.startUserMetaSync(docSnap);
341
+ } else {
342
+ setTimeout(() => {
343
+ this.waitForUser();
344
+ }, 1000);
345
+ }
346
+ }
347
+
332
348
  private setOnAuthStateChanged = (): void => {
333
349
  onAuthStateChanged(this.auth, (userAuth) => {
334
350
  if (userAuth) {
@@ -336,7 +352,7 @@ export const EdgeFirebase = class {
336
352
  this.user.uid = userAuth.uid;
337
353
  this.user.logInError = false;
338
354
  this.user.logInErrorMessage = "";
339
- this.startUserMetaSync();
355
+ this.waitForUser();
340
356
  } else {
341
357
  this.user.email = "";
342
358
  this.user.uid = null;
@@ -346,11 +362,17 @@ export const EdgeFirebase = class {
346
362
  }
347
363
  });
348
364
  };
349
-
350
365
  public registerUser = async (
351
366
  userRegister: userRegister
352
367
  ): Promise<actionResponse> => {
353
- const userRef = doc(this.db, "users", userRegister.email);
368
+ if (!Object.prototype.hasOwnProperty.call(userRegister, 'registrationCode') || userRegister.registrationCode === "") {
369
+ return this.sendResponse({
370
+ success: false,
371
+ message: "Registration code is required.",
372
+ meta: {}
373
+ });
374
+ }
375
+ const userRef = doc(this.db, "staged-users", userRegister.registrationCode);
354
376
  const userSnap = await getDoc(userRef);
355
377
  if (userSnap.exists()) {
356
378
  const user = userSnap.data();
@@ -361,29 +383,52 @@ export const EdgeFirebase = class {
361
383
  meta: {}
362
384
  });
363
385
  } else {
364
- createUserWithEmailAndPassword(
386
+ if (user.isTemplate && Object.prototype.hasOwnProperty.call("subCreate", user) && Object.values(user.subCreate).length > 0) {
387
+ if (!Object.prototype.hasOwnProperty.call(userRegister, 'dynamicDocumentFieldValue') || userRegister.dynamicDocumentFieldValue === "") {
388
+ return this.sendResponse({
389
+ success: false,
390
+ message: "Dynamic document field value is required for registration when template user has subCreate.",
391
+ meta: {}
392
+ });
393
+ }
394
+ }
395
+ // TODO: check if user is already registered, if so return error with auth
396
+ const response = await createUserWithEmailAndPassword(
365
397
  this.auth,
366
398
  userRegister.email,
367
399
  userRegister.password
368
- ).then(() => {
369
- const metaUpdate = {};
370
- for (const [key, value] of Object.entries(userRegister.meta)) {
371
- metaUpdate["meta." + key] = value;
372
- }
373
- if (Object.keys(metaUpdate).length > 0) {
374
- updateDoc(doc(this.db, "users", this.user.email), metaUpdate);
400
+ );
401
+
402
+ let metaUpdate = {};
403
+ if (Object.prototype.hasOwnProperty.call(userRegister, 'meta')) {
404
+ metaUpdate = userRegister.meta;
405
+ }else{
406
+ metaUpdate = user.meta;
407
+ }
408
+
409
+ let stagedUserUpdate: {userId?: string, templateUserId?: string, dynamicDocumentFieldValue?: string, uid: string, meta: unknown, templateMeta?: unknown} = {userId: response.user.uid, uid: response.user.uid, meta: metaUpdate}
410
+ if (user.isTemplate) {
411
+ stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, meta: user.meta, templateMeta: metaUpdate}
412
+ if (Object.prototype.hasOwnProperty.call(userRegister, 'dynamicDocumentFieldValue')) {
413
+ stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, dynamicDocumentFieldValue: userRegister.dynamicDocumentFieldValue, meta: user.meta, templateMeta: metaUpdate}
375
414
  }
376
- return this.sendResponse({
377
- success: true,
378
- message: "",
379
- meta: {}
380
- });
415
+ }
416
+ const initRoleHelper = {uid: response.user.uid}
417
+ initRoleHelper["edge-assignment-helper"] = {permissionType: "roles"}
418
+ await setDoc(doc(this.db, "rule-helpers", response.user.uid), initRoleHelper);
419
+ await updateDoc(doc(this.db, "staged-users/" + userRegister.registrationCode), stagedUserUpdate)
420
+
421
+ return this.sendResponse({
422
+ success: true,
423
+ message: "",
424
+ meta: {}
381
425
  });
426
+
382
427
  }
383
428
  } else {
384
429
  return this.sendResponse({
385
430
  success: false,
386
- message: "User doesn't exist",
431
+ message: "Registration code not valid.",
387
432
  meta: {}
388
433
  });
389
434
  }
@@ -453,10 +498,12 @@ export const EdgeFirebase = class {
453
498
  }
454
499
  };
455
500
 
456
- setUserMeta = async (meta: unknown): Promise<actionResponse> => {
501
+ public setUserMeta = async (meta: unknown): Promise<actionResponse> => {
502
+ //TODO: Change setUserMeta to also be used by someone with assign permissions and
503
+ // add a permissionCheck here for it.
457
504
  for (const [key, value] of Object.entries(meta)) {
458
- await updateDoc(doc(this.db, "users/" + this.user.email), {
459
- ["meta." + key]: value
505
+ await updateDoc(doc(this.db, "staged-users/" + this.user.stagedDocId), {
506
+ ["meta." + key]: value, uid: this.user.uid
460
507
  });
461
508
  }
462
509
  return this.sendResponse({
@@ -466,31 +513,31 @@ export const EdgeFirebase = class {
466
513
  });
467
514
  };
468
515
 
469
- public removeUser = async (email: string): Promise<actionResponse> => {
516
+ public removeUser = async (docId: string): Promise<actionResponse> => {
470
517
  const removedFrom = [];
471
- const userRef = doc(this.db, "users", email);
518
+ const userRef = doc(this.db, "users", docId);
472
519
  const userSnap = await getDoc(userRef);
473
520
  if (userSnap.data().roles) {
474
521
  for (const collectionPath in userSnap.data().roles) {
475
- const canAssign = this.permissionCheck(
522
+ const canAssign = await this.permissionCheck(
476
523
  "assign",
477
524
  collectionPath.replaceAll("-", "/")
478
525
  );
479
526
  if (canAssign) {
480
- await this.removeUserRoles(email, collectionPath.replaceAll("-", "/"));
527
+ await this.removeUserRoles(docId, collectionPath.replaceAll("-", "/"));
481
528
  removedFrom.push(collectionPath.replaceAll("-", "/"));
482
529
  }
483
530
  }
484
531
  }
485
532
  if (userSnap.data().specialPermissions) {
486
533
  for (const collectionPath in userSnap.data().specialPermissions) {
487
- const canAssign = this.permissionCheck(
534
+ const canAssign = await this.permissionCheck(
488
535
  "assign",
489
536
  collectionPath.replaceAll("-", "/")
490
537
  );
491
538
  if (canAssign) {
492
539
  this.removeUserSpecialPermissions(
493
- email,
540
+ docId,
494
541
  collectionPath.replaceAll("-", "/")
495
542
  );
496
543
  removedFrom.push(collectionPath.replaceAll("-", "/"));
@@ -511,8 +558,8 @@ export const EdgeFirebase = class {
511
558
  });
512
559
  }
513
560
  };
514
-
515
- public setUser = async (newUser: newUser): Promise<actionResponse> => {
561
+
562
+ public addUser = async (newUser: newUser): Promise<actionResponse> => {
516
563
  const canAssignRole = this.multiPermissionCheck(
517
564
  "assign",
518
565
  newUser.roles
@@ -522,30 +569,8 @@ export const EdgeFirebase = class {
522
569
  newUser.specialPermissions
523
570
  );
524
571
  if (canAssignRole.canDo && canAssignSpecialPermissions.canDo) {
525
- const userRef = doc(this.db, "users", newUser.email);
526
- const userSnap = await getDoc(userRef);
527
- if (!userSnap.exists()) {
528
- const userMeta: userMeta = {
529
- docId: newUser.email,
530
- userId: "",
531
- email: newUser.email,
532
- roles: newUser.roles,
533
- specialPermissions: newUser.specialPermissions,
534
- meta: newUser.meta
535
- };
536
- await this.generateUserMeta(userMeta);
537
- return this.sendResponse({
538
- success: true,
539
- message: "",
540
- meta: {}
541
- });
542
- } else {
543
- return this.sendResponse({
544
- success: false,
545
- message: "User already exists",
546
- meta: {}
547
- });
548
- }
572
+ const response = await this.generateUserMeta(newUser);
573
+ return this.sendResponse(response);
549
574
  } else {
550
575
  return this.sendResponse({
551
576
  success: false,
@@ -569,7 +594,7 @@ export const EdgeFirebase = class {
569
594
  // canDo = false;
570
595
  // }
571
596
  for (const collection of collections) {
572
- if (!(this.permissionCheck(action, collection.collectionPath))) {
597
+ if (!(this.permissionCheckOnly(action, collection.collectionPath))) {
573
598
  badCollectionPaths.push(collection.collectionPath);
574
599
  canDo = false;
575
600
  }
@@ -592,11 +617,52 @@ export const EdgeFirebase = class {
592
617
  return response;
593
618
  };
594
619
 
595
- private permissionCheck = (
596
- action: action,
597
- collectionPath: string
598
- ): boolean => {
599
- const collection = collectionPath.split("/");
620
+ private setRuleHelper = async(collectionPath: string, action): Promise<void> => {
621
+ const collection = collectionPath.replaceAll("-", "/").split("/");
622
+ let ruleKey = collectionPath.replaceAll("/", "-");
623
+ if (action === "assign") {
624
+ ruleKey = "edge-assignment-helper";
625
+ }
626
+ let index = collection.length;
627
+ const ruleCheck: RuleCheck = { permissionType: "", permissionCheckPath: "", fullPath: collectionPath.replaceAll("/", "-"), action };
628
+
629
+ while (index > 0) {
630
+ const collectionArray = JSON.parse(JSON.stringify(collection));
631
+ const permissionCheck = collectionArray.splice(0, index).join("-");
632
+ const role = this.user.roles.find(
633
+ (r) => r.collectionPath === permissionCheck
634
+ );
635
+ if (role) {
636
+ ruleCheck.permissionCheckPath = permissionCheck;
637
+ ruleCheck.permissionType = "roles";
638
+ }
639
+ const specialPermission = this.user.specialPermissions.find(
640
+ (r) => r.collectionPath === permissionCheck
641
+ );
642
+ if (specialPermission) {
643
+ ruleCheck.permissionCheckPath = permissionCheck;
644
+ ruleCheck.permissionType = "specialPermissions";
645
+ }
646
+ index--;
647
+ }
648
+ const rootRole = this.user.roles.find((r) => r.collectionPath === "-");
649
+ if (rootRole) {
650
+ ruleCheck.permissionCheckPath = "-";
651
+ ruleCheck.permissionType = "roles";
652
+ }
653
+ const rootSpecialPermission = this.user.specialPermissions.find(
654
+ (r) => r.collectionPath === "-"
655
+ );
656
+ if (rootSpecialPermission) {
657
+ ruleCheck.permissionCheckPath = "-";
658
+ ruleCheck.permissionType = "specialPermissions";
659
+ }
660
+ const check = {[ruleKey]: ruleCheck, uid: this.user.uid };
661
+ await setDoc(doc(this.db, "rule-helpers", this.user.uid), check, { merge: true });
662
+ }
663
+
664
+ public permissionCheckOnly = (action: action, collectionPath: string): boolean => {
665
+ const collection = collectionPath.replaceAll("-", "/").split("/");
600
666
  let index = collection.length;
601
667
  let permissionData = {};
602
668
  permissionData = {
@@ -644,6 +710,17 @@ export const EdgeFirebase = class {
644
710
  }
645
711
  }
646
712
  return permissionData[action];
713
+ }
714
+
715
+ public permissionCheck = async(
716
+ action: action,
717
+ collectionPath: string,
718
+ ): Promise<boolean> => {
719
+ const check = this.permissionCheckOnly(action, collectionPath);
720
+ if (check) {
721
+ await this.setRuleHelper(collectionPath, action);
722
+ }
723
+ return check;
647
724
  };
648
725
 
649
726
  private getCollectionPermissions = (
@@ -672,48 +749,78 @@ export const EdgeFirebase = class {
672
749
  };
673
750
  };
674
751
 
675
- private generateUserMeta = async (userMeta: userMeta): Promise<void> => {
752
+ private generateUserMeta = async (userMeta: newUser): Promise<actionResponse> => {
676
753
  const roles: role[] = userMeta.roles;
677
754
  const specialPermissions: specialPermission[] = userMeta.specialPermissions;
678
755
  delete userMeta.roles;
679
756
  delete userMeta.specialPermissions;
680
757
 
681
- const docRef = doc(this.db, "users", userMeta.docId);
682
- const docSnap = await getDoc(docRef);
683
- const docData = docSnap.data();
684
- const canWrite = this.permissionCheck("write", "users");
685
- if (!docData || canWrite) {
686
- await setDoc(doc(this.db, "users", userMeta.docId), userMeta);
758
+ let isTemplate = false
759
+ if (Object.prototype.hasOwnProperty.call(userMeta, "isTemplate") && userMeta.isTemplate) {
760
+ isTemplate = true
687
761
  }
762
+
763
+ let subCreate = {}
764
+ if (Object.prototype.hasOwnProperty.call(userMeta, "subCreate")) {
765
+ if (userMeta.subCreate.rootPath.split("-").length % 2==0){
766
+ return {
767
+ success: false,
768
+ message: "subCreate.rootPath must contain an odd number of segments.",
769
+ meta: {}
770
+ }
771
+ }
772
+ const canAssign = await this.permissionCheck("assign", userMeta.subCreate.rootPath)
773
+ if (canAssign) {
774
+ subCreate = userMeta.subCreate
775
+ } else {
776
+ return {
777
+ success: false,
778
+ message: "You do not have assign permission to '" + userMeta.subCreate.rootPath + "'",
779
+ meta: {}
780
+ }
781
+ }
782
+ }
783
+
784
+ const onlyMeta = { meta: userMeta.meta, userId: "", uid: this.user.uid, roles:{}, specialPermissions:{}, isTemplate, subCreate };
785
+
786
+ const docRef = await addDoc(collection(this.db, "staged-users"), onlyMeta );
688
787
  for (const role of roles) {
689
- await this.storeUserRoles(userMeta.docId, role.collectionPath, role.role);
788
+ await this.storeUserRoles(docRef.id, role.collectionPath, role.role);
690
789
  }
691
790
  for (const specialPermission of specialPermissions) {
692
791
  await this.storeUserSpecialPermissions(
693
- userMeta.docId,
792
+ docRef.id,
694
793
  specialPermission.collectionPath,
695
794
  specialPermission.permissions
696
795
  );
697
796
  }
797
+ return {
798
+ success: true,
799
+ message: "",
800
+ meta: {}
801
+ }
698
802
  };
699
803
 
700
- // Composable to logout
701
804
  public logOut = (): void => {
702
- signOut(this.auth)
703
- .then(() => {
704
- Object.keys(this.unsubscibe).forEach((key) => {
705
- if (this.unsubscibe[key] instanceof Function) {
706
- this.unsubscibe[key]();
707
- this.unsubscibe[key] = null;
708
- }
709
- });
710
- })
711
- .catch(() => {
712
- // Do nothing
713
- });
805
+ for (const key of Object.keys(this.unsubscibe)) {
806
+ if (this.unsubscibe[key] instanceof Function) {
807
+ this.unsubscibe[key]();
808
+ this.unsubscibe[key] = null;
809
+ }
810
+ }
811
+ signOut(this.auth).then(() => {
812
+ this.user.uid = null;
813
+ this.user.email = "";
814
+ this.user.loggedIn = false;
815
+ this.user.logInError = false;
816
+ this.user.logInErrorMessage = "";
817
+ this.user.meta = {};
818
+ this.user.roles = [];
819
+ this.user.specialPermissions = [];
820
+ this.user.stagedDocId = null;
821
+ })
714
822
  };
715
823
 
716
- // Composable to login and set persistence
717
824
  public logIn = (credentials: Credentials): void => {
718
825
  this.logOut();
719
826
  signInWithEmailAndPassword(
@@ -757,22 +864,33 @@ export const EdgeFirebase = class {
757
864
  meta: {},
758
865
  roles: [],
759
866
  specialPermissions: [],
867
+ stagedDocId: null,
760
868
  });
761
869
 
762
870
  public state = reactive({
763
871
  collectionPermissions: {},
764
872
  users: {},
873
+ registrationCode: "",
874
+ registrationMeta: {},
765
875
  });
766
876
 
767
877
  public getDocData = async (
768
878
  collectionPath: string,
769
879
  docId: string
770
880
  ): Promise<{ [key: string]: unknown }> => {
771
- const docRef = doc(this.db, collectionPath, docId);
772
- const docSnap = await getDoc(docRef);
773
- const docData = docSnap.data();
774
- docData.docId = docSnap.id;
775
- return docData;
881
+ const canRead = await this.permissionCheck("read", collectionPath + "/" + docId);
882
+ if (canRead) {
883
+ const docRef = doc(this.db, collectionPath, docId);
884
+ const docSnap = await getDoc(docRef);
885
+ const docData = docSnap.data();
886
+ docData.docId = docSnap.id;
887
+ return docData;
888
+ }
889
+ return {
890
+ success: false,
891
+ message: "Permission Denied",
892
+ meta: {}
893
+ }
776
894
  };
777
895
 
778
896
  private collectionExists = (
@@ -789,25 +907,27 @@ export const EdgeFirebase = class {
789
907
  last: DocumentData | null = null
790
908
  ): Promise<StaticDataResult> => {
791
909
  const data: object = {};
910
+ let nextLast: DocumentData | null = null;
911
+ const canRead = await this.permissionCheck("read", collectionPath);
912
+ if (canRead) {
913
+ const q = this.getQuery(collectionPath, queryList, orderList, max, last);
792
914
 
793
- const q = this.getQuery(collectionPath, queryList, orderList, max, last);
794
-
795
- const docs = await getDocs(q);
796
-
797
- const nextLast: DocumentData = docs.docs[docs.docs.length - 1];
798
- docs.forEach((doc) => {
799
- const item = doc.data();
800
- item.docId = doc.id;
801
- data[doc.id] = item;
802
- });
915
+ const docs = await getDocs(q);
803
916
 
917
+ nextLast = docs.docs[docs.docs.length - 1];
918
+ docs.forEach((doc) => {
919
+ const item = doc.data();
920
+ item.docId = doc.id;
921
+ data[doc.id] = item;
922
+ });
923
+ }
804
924
  return { data, next: nextLast };
805
925
  };
806
926
 
807
927
  // Class for wrapping a getSaticData to handle pagination
808
928
  get SearchStaticData() {
809
929
  const getStaticData = this.getStaticData;
810
- const permissionCheck = this.permissionCheck;
930
+ const permissionCheckOnly = this.permissionCheckOnly;
811
931
  const sendResponse = this.sendResponse;
812
932
  return class {
813
933
  private collectionPath = "";
@@ -910,7 +1030,7 @@ export const EdgeFirebase = class {
910
1030
  orderList: FirestoreOrderBy[] = [],
911
1031
  max = 0
912
1032
  ): Promise<actionResponse> => {
913
- const canRead = permissionCheck("read", collectionPath);
1033
+ const canRead = permissionCheckOnly("read", collectionPath);
914
1034
 
915
1035
  if (canRead) {
916
1036
  this.collectionPath = collectionPath;
@@ -996,13 +1116,13 @@ export const EdgeFirebase = class {
996
1116
  );
997
1117
  };
998
1118
 
999
- public startSnapshot = (
1119
+ public startSnapshot = async(
1000
1120
  collectionPath: string,
1001
1121
  queryList: FirestoreQuery[] = [],
1002
1122
  orderList: FirestoreOrderBy[] = [],
1003
1123
  max = 0
1004
- ): actionResponse => {
1005
- const canRead = this.permissionCheck("read", collectionPath);
1124
+ ): Promise<actionResponse> => {
1125
+ const canRead = await this.permissionCheck("read", collectionPath);
1006
1126
  this.data[collectionPath] = {};
1007
1127
  this.stopSnapshot(collectionPath);
1008
1128
  this.unsubscibe[collectionPath] = null;
@@ -1032,73 +1152,85 @@ export const EdgeFirebase = class {
1032
1152
  }
1033
1153
  };
1034
1154
 
1035
- public startUsersSnapshot = (collectionPath = ''): void => {
1036
- this.stopSnapshot('users')
1037
- this.state.users = {};
1038
- if (collectionPath) {
1039
- if (this.permissionCheck('assign', collectionPath)) {
1040
- const q = query(
1041
- collection(this.db, "users"),
1042
- where(
1043
- "collectionPaths",
1044
- "array-contains",
1045
- collectionPath.replaceAll('/', '-')
1155
+ private usersSnapshotStarting = false;
1156
+
1157
+ public startUsersSnapshot = async(collectionPath = ''): Promise<void> => {
1158
+ if (!this.usersSnapshotStarting) {
1159
+ this.usersSnapshotStarting = true;
1160
+ this.stopSnapshot("staged-users");
1161
+ this.state.users = {};
1162
+ if (collectionPath) {
1163
+ const canAssign = await this.permissionCheck('assign', collectionPath);
1164
+ if (canAssign) {
1165
+ const q = query(
1166
+ collection(this.db, "staged-users"),
1167
+ where(
1168
+ "collectionPaths",
1169
+ "array-contains",
1170
+ collectionPath.replaceAll('/', '-')
1171
+ )
1046
1172
  )
1047
- )
1048
- const unsubscibe = onSnapshot(q, (querySnapshot) => {
1049
- const items = {};
1050
- querySnapshot.forEach((doc) => {
1051
- const user = doc.data();
1052
- const newRoles = [];
1053
- const newSpecialPermissions = [];
1054
-
1055
- if (user.roles) {
1056
- const roles: role[] = Object.values(user.roles);
1057
- roles.forEach((role) => {
1058
- if (this.permissionCheck('assign', role.collectionPath)) {
1059
- newRoles.push(role)
1173
+ const unsubscibe = await onSnapshot(q, (querySnapshot) => {
1174
+ const items = {};
1175
+ querySnapshot.forEach((doc) => {
1176
+ const user = doc.data();
1177
+ const docId = doc.id;
1178
+ const newRoles = [];
1179
+ const newSpecialPermissions = [];
1180
+
1181
+ if (user.roles) {
1182
+ const roles: role[] = Object.values(user.roles);
1183
+ for (const role of roles) {
1184
+ if (this.permissionCheckOnly('assign', role.collectionPath)) {
1185
+ newRoles.push(role)
1186
+ }
1060
1187
  }
1061
- });
1062
- }
1063
- if (user.specialPermissions) {
1064
- const permissions: specialPermission[] = Object.values(user.specialPermissions);
1065
- permissions.forEach(permission => {
1066
- if (this.permissionCheck('assign', permission.collectionPath)) {
1067
- newSpecialPermissions.push(permission)
1188
+ }
1189
+ if (user.specialPermissions) {
1190
+ const permissions: specialPermission[] = Object.values(user.specialPermissions);
1191
+ for (const permission of permissions) {
1192
+ if (this.permissionCheckOnly('assign', permission.collectionPath)) {
1193
+ newSpecialPermissions.push(permission)
1194
+ }
1068
1195
  }
1069
- });
1070
- }
1071
- const item = {
1072
- docId: user.docId,
1073
- email: user.email,
1074
- roles: newRoles,
1075
- specialPermissions: newSpecialPermissions,
1076
- meta: user.meta,
1077
- last_updated: user.last_updated,
1078
- userId: user.userId,
1079
- uid: user.uid
1080
- }
1081
- items[doc.id] = item;
1196
+ }
1197
+ const item = {
1198
+ docId,
1199
+ email: user.email,
1200
+ roles: newRoles,
1201
+ specialPermissions: newSpecialPermissions,
1202
+ meta: user.meta,
1203
+ last_updated: user.last_updated,
1204
+ isTemplate: user.isTemplate,
1205
+ subCreate: user.subCreate,
1206
+ userId: user.userId,
1207
+ uid: user.uid
1208
+ }
1209
+ items[doc.id] = item;
1210
+ });
1211
+ this.state.users = items;
1082
1212
  });
1083
- this.state.users = items;
1084
- });
1085
- this.unsubscibe.users = unsubscibe;
1213
+ this.unsubscibe["staged-users"] = unsubscibe;
1214
+ }
1086
1215
  }
1087
- }
1216
+ };
1217
+ this.usersSnapshotStarting = false;
1088
1218
  };
1089
1219
 
1090
1220
  public removeUserRoles = async (
1091
- email: string,
1221
+ docId: string,
1092
1222
  collectionPath: string
1093
1223
  ): Promise<actionResponse> => {
1094
- const canAssign = this.permissionCheck("assign", collectionPath);
1224
+ let canAssign = await this.permissionCheck("assign", collectionPath);
1225
+ if (docId === this.user.stagedDocId) {
1226
+ // User can remove themselves from any role
1227
+ canAssign = true;
1228
+ }
1095
1229
  if (canAssign) {
1096
- await updateDoc(doc(this.db, "users/" + email), {
1230
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1231
+ collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
1097
1232
  ["roles." + collectionPath.replaceAll("/", "-")]: deleteField()
1098
1233
  });
1099
- await updateDoc(doc(this.db, "users/" + email), {
1100
- collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-"))
1101
- });
1102
1234
  return this.sendResponse({
1103
1235
  success: true,
1104
1236
  message: "",
@@ -1115,18 +1247,20 @@ export const EdgeFirebase = class {
1115
1247
  };
1116
1248
 
1117
1249
  public removeUserSpecialPermissions = async (
1118
- email: string,
1250
+ docId: string,
1119
1251
  collectionPath: string
1120
1252
  ): Promise<actionResponse> => {
1121
- const canAssign = this.permissionCheck("assign", collectionPath);
1253
+ let canAssign = await this.permissionCheck("assign", collectionPath);
1254
+ if (docId === this.user.stagedDocId) {
1255
+ // User can remove themselves from any special permission
1256
+ canAssign = true;
1257
+ }
1122
1258
  if (canAssign) {
1123
- await updateDoc(doc(this.db, "users/" + email), {
1259
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1260
+ collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
1124
1261
  ["specialPermissions." + collectionPath.replaceAll("/", "-")]:
1125
1262
  deleteField()
1126
1263
  });
1127
- await updateDoc(doc(this.db, "users/" + email), {
1128
- collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-"))
1129
- });
1130
1264
  return this.sendResponse({
1131
1265
  success: true,
1132
1266
  message: "",
@@ -1143,11 +1277,11 @@ export const EdgeFirebase = class {
1143
1277
  };
1144
1278
 
1145
1279
  public storeUserSpecialPermissions = async (
1146
- email: string,
1280
+ docId: string,
1147
1281
  collectionPath: string,
1148
1282
  permissions: permissions
1149
1283
  ): Promise<actionResponse> => {
1150
- const canAssign = this.permissionCheck("assign", collectionPath);
1284
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1151
1285
  if (canAssign) {
1152
1286
  const collectionExists = await this.collectionExists(collectionPath);
1153
1287
  if (collectionExists) {
@@ -1155,11 +1289,13 @@ export const EdgeFirebase = class {
1155
1289
  ["specialPermissions." + collectionPath.replaceAll("/", "-")]: {
1156
1290
  collectionPath: collectionPath.replaceAll("/", "-"),
1157
1291
  permissions
1158
- }
1292
+ },
1293
+ uid: this.user.uid
1159
1294
  };
1160
- await updateDoc(doc(this.db, "users/" + email), permissionItem);
1161
- await updateDoc(doc(this.db, "users/" + email), {
1162
- collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-"))
1295
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1296
+ ...permissionItem,
1297
+ collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-")),
1298
+ uid: this.user.uid
1163
1299
  });
1164
1300
  return this.sendResponse({
1165
1301
  success: true,
@@ -1184,12 +1320,11 @@ export const EdgeFirebase = class {
1184
1320
  };
1185
1321
 
1186
1322
  private storeUserRoles = async (
1187
- email: string,
1323
+ docId: string,
1188
1324
  collectionPath: string,
1189
1325
  role: "admin" | "editor" | "writer" | "user"
1190
1326
  ): Promise<actionResponse> => {
1191
- const canAssign = this.permissionCheck("assign", collectionPath);
1192
-
1327
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1193
1328
  if (canAssign) {
1194
1329
  if (role === "admin" || role === "user" || role === "editor" || role === "writer") {
1195
1330
  const collectionExists = await this.collectionExists(collectionPath);
@@ -1198,13 +1333,13 @@ export const EdgeFirebase = class {
1198
1333
  ["roles." + collectionPath.replaceAll("/", "-")]: {
1199
1334
  collectionPath: collectionPath.replaceAll("/", "-"),
1200
1335
  role
1201
- }
1336
+ },
1337
+ uid: this.user.uid
1202
1338
  };
1203
-
1204
- await updateDoc(doc(this.db, "users/" + email), roleItem);
1205
- await updateDoc(doc(this.db, "users/" + email), {
1206
- collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-"))
1207
- });
1339
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1340
+ ...roleItem,
1341
+ collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-")),
1342
+ uid: this.user.uid } );
1208
1343
  return this.sendResponse({
1209
1344
  success: true,
1210
1345
  message: "",
@@ -1237,7 +1372,7 @@ export const EdgeFirebase = class {
1237
1372
  public removeCollectionPermissions = async (
1238
1373
  collectionPath: string,
1239
1374
  ): Promise<actionResponse> => {
1240
- const canAssign = this.permissionCheck("assign", collectionPath);
1375
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1241
1376
  if (canAssign) {
1242
1377
  await deleteDoc(doc(this.db, "collection-data", collectionPath.replaceAll("/", "-")));
1243
1378
  return this.sendResponse({
@@ -1259,8 +1394,7 @@ export const EdgeFirebase = class {
1259
1394
  role: "admin" | "editor" | "writer" | "user",
1260
1395
  permissions: permissions
1261
1396
  ): Promise<actionResponse> => {
1262
- const canAssign = this.permissionCheck("assign", collectionPath);
1263
- // TODO: check if collectionPath starts with "users" and deny if so
1397
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1264
1398
  if (canAssign) {
1265
1399
  if (role === "admin" || role === "editor" || role === "writer" || role === "user") {
1266
1400
  const currentTime = new Date().getTime();
@@ -1313,7 +1447,7 @@ export const EdgeFirebase = class {
1313
1447
  collectionPath: string,
1314
1448
  item: object,
1315
1449
  ): Promise<actionResponse> => {
1316
- const canWrite = this.permissionCheck("write", collectionPath);
1450
+ const canWrite = await this.permissionCheck("write", collectionPath);
1317
1451
  if (!canWrite) {
1318
1452
  return this.sendResponse({
1319
1453
  success: false,
@@ -1330,7 +1464,7 @@ export const EdgeFirebase = class {
1330
1464
  }
1331
1465
  if (Object.prototype.hasOwnProperty.call(cloneItem, "docId")) {
1332
1466
  const docId = cloneItem.docId;
1333
- const canRead = this.permissionCheck("read", collectionPath);
1467
+ const canRead = this.permissionCheckOnly("read", collectionPath);
1334
1468
  if (canRead) {
1335
1469
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1336
1470
  this.data[collectionPath][docId] = cloneItem;
@@ -1347,7 +1481,7 @@ export const EdgeFirebase = class {
1347
1481
  collection(this.db, collectionPath),
1348
1482
  cloneItem
1349
1483
  );
1350
- const canRead = this.permissionCheck("read", collectionPath);
1484
+ const canRead = this.permissionCheckOnly("read", collectionPath);
1351
1485
  if (canRead) {
1352
1486
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1353
1487
  this.data[collectionPath][docRef.id] = cloneItem;
@@ -1371,7 +1505,7 @@ export const EdgeFirebase = class {
1371
1505
  collectionPath: string,
1372
1506
  docId: string
1373
1507
  ): Promise<actionResponse> => {
1374
- const canDelete = this.permissionCheck("delete", collectionPath);
1508
+ const canDelete = await this.permissionCheck("delete", collectionPath);
1375
1509
  if (canDelete) {
1376
1510
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1377
1511
  if (
@@ -1402,4 +1536,4 @@ export const EdgeFirebase = class {
1402
1536
  this.unsubscibe[collectionPath] = null;
1403
1537
  }
1404
1538
  };
1405
- };
1539
+ };