@edgedev/firebase 1.6.2 → 1.7.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.
Files changed (3) hide show
  1. package/README.md +463 -19
  2. package/edgeFirebase.ts +367 -288
  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(
@@ -745,64 +852,9 @@ export const EdgeFirebase = class {
745
852
 
746
853
  // Simple Store Items (add matching key per firebase collection)
747
854
  public data: CollectionDataObject = reactive({});
748
- private usersByCollections: CollectionDataObject = reactive({});
749
-
750
- public users = computed(() => {
751
- const userList = {};
752
- const keys = Object.keys(JSON.parse(JSON.stringify(this.usersByCollections)));
753
- keys.forEach(key => {
754
- const users = this.usersByCollections[key];
755
- if (key.startsWith("ROLES|")) {
756
- const collectionPathCheck = key.replace("ROLES|", "")
757
- const userKeys = Object.keys(users);
758
- if (Object.keys(users).length > 0) {
759
- userKeys.forEach(userKey => {
760
- const user = users[userKey];
761
- if (!Object.prototype.hasOwnProperty.call(userList, user.docId)) {
762
- userList[user.email] = {
763
- docId: user.docId,
764
- email: user.email,
765
- roles: [{collectionPath: collectionPathCheck, role: user.roles[collectionPathCheck].role }],
766
- specialPermissions: [],
767
- meta: user.meta,
768
- last_updated: user.last_updated,
769
- userId: user.userId,
770
- uid: user.uid
771
- }
772
- } else {
773
- userList[user.email].roles.push({ collectionPath: collectionPathCheck, role: user.roles[collectionPathCheck].role })
774
- }
775
- });
776
- }
777
- }
778
- if (key.startsWith("SPECIALPERMISSIONS|")) {
779
- const collectionPathCheck = key.replace("SPECIALPERMISSIONS|", "")
780
- const userKeys = Object.keys(users);
781
- if (Object.keys(users).length > 0) {
782
- userKeys.forEach(userKey => {
783
- const user = users[userKey];
784
- if (!Object.prototype.hasOwnProperty.call(userList, user.docId)) {
785
- userList[user.email] = {
786
- docId: user.docId,
787
- email: user.email,
788
- roles: [],
789
- specialPermissions: [{ collectionPath: collectionPathCheck, permissions: user.specialPermissions[collectionPathCheck].permissions }],
790
- meta: user.meta,
791
- last_updated: user.last_updated,
792
- userId: user.userId,
793
- uid: user.uid
794
- }
795
- } else {
796
- userList[user.email].specialPermissions.push({ collectionPath: collectionPathCheck, permissions: user.specialPermissions[collectionPathCheck].permissions })
797
- }
798
- });
799
- }
800
- }
801
- });
802
- return userList;
803
- });
804
855
 
805
856
  public unsubscibe: CollectionUnsubscribeObject = reactive({});
857
+
806
858
  public user: UserDataObject = reactive({
807
859
  uid: null,
808
860
  email: "",
@@ -812,22 +864,33 @@ export const EdgeFirebase = class {
812
864
  meta: {},
813
865
  roles: [],
814
866
  specialPermissions: [],
867
+ stagedDocId: null,
815
868
  });
816
869
 
817
870
  public state = reactive({
818
871
  collectionPermissions: {},
819
872
  users: {},
873
+ registrationCode: "",
874
+ registrationMeta: {},
820
875
  });
821
876
 
822
877
  public getDocData = async (
823
878
  collectionPath: string,
824
879
  docId: string
825
880
  ): Promise<{ [key: string]: unknown }> => {
826
- const docRef = doc(this.db, collectionPath, docId);
827
- const docSnap = await getDoc(docRef);
828
- const docData = docSnap.data();
829
- docData.docId = docSnap.id;
830
- 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
+ }
831
894
  };
832
895
 
833
896
  private collectionExists = (
@@ -844,25 +907,27 @@ export const EdgeFirebase = class {
844
907
  last: DocumentData | null = null
845
908
  ): Promise<StaticDataResult> => {
846
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);
847
914
 
848
- const q = this.getQuery(collectionPath, queryList, orderList, max, last);
849
-
850
- const docs = await getDocs(q);
851
-
852
- const nextLast: DocumentData = docs.docs[docs.docs.length - 1];
853
- docs.forEach((doc) => {
854
- const item = doc.data();
855
- item.docId = doc.id;
856
- data[doc.id] = item;
857
- });
915
+ const docs = await getDocs(q);
858
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
+ }
859
924
  return { data, next: nextLast };
860
925
  };
861
926
 
862
927
  // Class for wrapping a getSaticData to handle pagination
863
928
  get SearchStaticData() {
864
929
  const getStaticData = this.getStaticData;
865
- const permissionCheck = this.permissionCheck;
930
+ const permissionCheckOnly = this.permissionCheckOnly;
866
931
  const sendResponse = this.sendResponse;
867
932
  return class {
868
933
  private collectionPath = "";
@@ -965,7 +1030,7 @@ export const EdgeFirebase = class {
965
1030
  orderList: FirestoreOrderBy[] = [],
966
1031
  max = 0
967
1032
  ): Promise<actionResponse> => {
968
- const canRead = permissionCheck("read", collectionPath);
1033
+ const canRead = permissionCheckOnly("read", collectionPath);
969
1034
 
970
1035
  if (canRead) {
971
1036
  this.collectionPath = collectionPath;
@@ -1051,13 +1116,13 @@ export const EdgeFirebase = class {
1051
1116
  );
1052
1117
  };
1053
1118
 
1054
- public startSnapshot = (
1119
+ public startSnapshot = async(
1055
1120
  collectionPath: string,
1056
1121
  queryList: FirestoreQuery[] = [],
1057
1122
  orderList: FirestoreOrderBy[] = [],
1058
1123
  max = 0
1059
- ): actionResponse => {
1060
- const canRead = this.permissionCheck("read", collectionPath);
1124
+ ): Promise<actionResponse> => {
1125
+ const canRead = await this.permissionCheck("read", collectionPath);
1061
1126
  this.data[collectionPath] = {};
1062
1127
  this.stopSnapshot(collectionPath);
1063
1128
  this.unsubscibe[collectionPath] = null;
@@ -1087,73 +1152,85 @@ export const EdgeFirebase = class {
1087
1152
  }
1088
1153
  };
1089
1154
 
1090
- public startUsersSnapshot = (collectionPath = ''): void => {
1091
- this.stopSnapshot('users')
1092
- this.state.users = {};
1093
- if (collectionPath) {
1094
- if (this.permissionCheck('assign', collectionPath)) {
1095
- const q = query(
1096
- collection(this.db, "users"),
1097
- where(
1098
- "collectionPaths",
1099
- "array-contains",
1100
- 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
+ )
1101
1172
  )
1102
- )
1103
- const unsubscibe = onSnapshot(q, (querySnapshot) => {
1104
- const items = {};
1105
- querySnapshot.forEach((doc) => {
1106
- const user = doc.data();
1107
- const newRoles = [];
1108
- const newSpecialPermissions = [];
1109
-
1110
- if (user.roles) {
1111
- const roles: role[] = Object.values(user.roles);
1112
- roles.forEach((role) => {
1113
- if (this.permissionCheck('assign', role.collectionPath)) {
1114
- 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
+ }
1115
1187
  }
1116
- });
1117
- }
1118
- if (user.specialPermissions) {
1119
- const permissions: specialPermission[] = Object.values(user.specialPermissions);
1120
- permissions.forEach(permission => {
1121
- if (this.permissionCheck('assign', permission.collectionPath)) {
1122
- 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
+ }
1123
1195
  }
1124
- });
1125
- }
1126
- const item = {
1127
- docId: user.docId,
1128
- email: user.email,
1129
- roles: newRoles,
1130
- specialPermissions: newSpecialPermissions,
1131
- meta: user.meta,
1132
- last_updated: user.last_updated,
1133
- userId: user.userId,
1134
- uid: user.uid
1135
- }
1136
- 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;
1137
1212
  });
1138
- this.state.users = items;
1139
- });
1140
- this.unsubscibe.users = unsubscibe;
1213
+ this.unsubscibe["staged-users"] = unsubscibe;
1214
+ }
1141
1215
  }
1142
- }
1216
+ };
1217
+ this.usersSnapshotStarting = false;
1143
1218
  };
1144
1219
 
1145
1220
  public removeUserRoles = async (
1146
- email: string,
1221
+ docId: string,
1147
1222
  collectionPath: string
1148
1223
  ): Promise<actionResponse> => {
1149
- 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
+ }
1150
1229
  if (canAssign) {
1151
- await updateDoc(doc(this.db, "users/" + email), {
1230
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1231
+ collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
1152
1232
  ["roles." + collectionPath.replaceAll("/", "-")]: deleteField()
1153
1233
  });
1154
- await updateDoc(doc(this.db, "users/" + email), {
1155
- collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-"))
1156
- });
1157
1234
  return this.sendResponse({
1158
1235
  success: true,
1159
1236
  message: "",
@@ -1170,18 +1247,20 @@ export const EdgeFirebase = class {
1170
1247
  };
1171
1248
 
1172
1249
  public removeUserSpecialPermissions = async (
1173
- email: string,
1250
+ docId: string,
1174
1251
  collectionPath: string
1175
1252
  ): Promise<actionResponse> => {
1176
- 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
+ }
1177
1258
  if (canAssign) {
1178
- await updateDoc(doc(this.db, "users/" + email), {
1259
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1260
+ collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
1179
1261
  ["specialPermissions." + collectionPath.replaceAll("/", "-")]:
1180
1262
  deleteField()
1181
1263
  });
1182
- await updateDoc(doc(this.db, "users/" + email), {
1183
- collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-"))
1184
- });
1185
1264
  return this.sendResponse({
1186
1265
  success: true,
1187
1266
  message: "",
@@ -1198,11 +1277,11 @@ export const EdgeFirebase = class {
1198
1277
  };
1199
1278
 
1200
1279
  public storeUserSpecialPermissions = async (
1201
- email: string,
1280
+ docId: string,
1202
1281
  collectionPath: string,
1203
1282
  permissions: permissions
1204
1283
  ): Promise<actionResponse> => {
1205
- const canAssign = this.permissionCheck("assign", collectionPath);
1284
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1206
1285
  if (canAssign) {
1207
1286
  const collectionExists = await this.collectionExists(collectionPath);
1208
1287
  if (collectionExists) {
@@ -1210,11 +1289,13 @@ export const EdgeFirebase = class {
1210
1289
  ["specialPermissions." + collectionPath.replaceAll("/", "-")]: {
1211
1290
  collectionPath: collectionPath.replaceAll("/", "-"),
1212
1291
  permissions
1213
- }
1292
+ },
1293
+ uid: this.user.uid
1214
1294
  };
1215
- await updateDoc(doc(this.db, "users/" + email), permissionItem);
1216
- await updateDoc(doc(this.db, "users/" + email), {
1217
- 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
1218
1299
  });
1219
1300
  return this.sendResponse({
1220
1301
  success: true,
@@ -1239,12 +1320,11 @@ export const EdgeFirebase = class {
1239
1320
  };
1240
1321
 
1241
1322
  private storeUserRoles = async (
1242
- email: string,
1323
+ docId: string,
1243
1324
  collectionPath: string,
1244
1325
  role: "admin" | "editor" | "writer" | "user"
1245
1326
  ): Promise<actionResponse> => {
1246
- const canAssign = this.permissionCheck("assign", collectionPath);
1247
-
1327
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1248
1328
  if (canAssign) {
1249
1329
  if (role === "admin" || role === "user" || role === "editor" || role === "writer") {
1250
1330
  const collectionExists = await this.collectionExists(collectionPath);
@@ -1253,13 +1333,13 @@ export const EdgeFirebase = class {
1253
1333
  ["roles." + collectionPath.replaceAll("/", "-")]: {
1254
1334
  collectionPath: collectionPath.replaceAll("/", "-"),
1255
1335
  role
1256
- }
1336
+ },
1337
+ uid: this.user.uid
1257
1338
  };
1258
-
1259
- await updateDoc(doc(this.db, "users/" + email), roleItem);
1260
- await updateDoc(doc(this.db, "users/" + email), {
1261
- collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-"))
1262
- });
1339
+ await updateDoc(doc(this.db, "staged-users/" + docId), {
1340
+ ...roleItem,
1341
+ collectionPaths: arrayUnion(collectionPath.replaceAll("/", "-")),
1342
+ uid: this.user.uid } );
1263
1343
  return this.sendResponse({
1264
1344
  success: true,
1265
1345
  message: "",
@@ -1292,7 +1372,7 @@ export const EdgeFirebase = class {
1292
1372
  public removeCollectionPermissions = async (
1293
1373
  collectionPath: string,
1294
1374
  ): Promise<actionResponse> => {
1295
- const canAssign = this.permissionCheck("assign", collectionPath);
1375
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1296
1376
  if (canAssign) {
1297
1377
  await deleteDoc(doc(this.db, "collection-data", collectionPath.replaceAll("/", "-")));
1298
1378
  return this.sendResponse({
@@ -1314,8 +1394,7 @@ export const EdgeFirebase = class {
1314
1394
  role: "admin" | "editor" | "writer" | "user",
1315
1395
  permissions: permissions
1316
1396
  ): Promise<actionResponse> => {
1317
- const canAssign = this.permissionCheck("assign", collectionPath);
1318
- // TODO: check if collectionPath starts with "users" and deny if so
1397
+ const canAssign = await this.permissionCheck("assign", collectionPath);
1319
1398
  if (canAssign) {
1320
1399
  if (role === "admin" || role === "editor" || role === "writer" || role === "user") {
1321
1400
  const currentTime = new Date().getTime();
@@ -1368,7 +1447,7 @@ export const EdgeFirebase = class {
1368
1447
  collectionPath: string,
1369
1448
  item: object,
1370
1449
  ): Promise<actionResponse> => {
1371
- const canWrite = this.permissionCheck("write", collectionPath);
1450
+ const canWrite = await this.permissionCheck("write", collectionPath);
1372
1451
  if (!canWrite) {
1373
1452
  return this.sendResponse({
1374
1453
  success: false,
@@ -1385,7 +1464,7 @@ export const EdgeFirebase = class {
1385
1464
  }
1386
1465
  if (Object.prototype.hasOwnProperty.call(cloneItem, "docId")) {
1387
1466
  const docId = cloneItem.docId;
1388
- const canRead = this.permissionCheck("read", collectionPath);
1467
+ const canRead = this.permissionCheckOnly("read", collectionPath);
1389
1468
  if (canRead) {
1390
1469
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1391
1470
  this.data[collectionPath][docId] = cloneItem;
@@ -1402,7 +1481,7 @@ export const EdgeFirebase = class {
1402
1481
  collection(this.db, collectionPath),
1403
1482
  cloneItem
1404
1483
  );
1405
- const canRead = this.permissionCheck("read", collectionPath);
1484
+ const canRead = this.permissionCheckOnly("read", collectionPath);
1406
1485
  if (canRead) {
1407
1486
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1408
1487
  this.data[collectionPath][docRef.id] = cloneItem;
@@ -1426,7 +1505,7 @@ export const EdgeFirebase = class {
1426
1505
  collectionPath: string,
1427
1506
  docId: string
1428
1507
  ): Promise<actionResponse> => {
1429
- const canDelete = this.permissionCheck("delete", collectionPath);
1508
+ const canDelete = await this.permissionCheck("delete", collectionPath);
1430
1509
  if (canDelete) {
1431
1510
  if (Object.prototype.hasOwnProperty.call(this.data, collectionPath)) {
1432
1511
  if (
@@ -1457,4 +1536,4 @@ export const EdgeFirebase = class {
1457
1536
  this.unsubscibe[collectionPath] = null;
1458
1537
  }
1459
1538
  };
1460
- };
1539
+ };