@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.
- package/README.md +465 -20
- package/edgeFirebase.ts +366 -232
- package/package.json +1 -1
package/edgeFirebase.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { initializeApp } from "firebase/app";
|
|
2
|
-
import { reactive
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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.
|
|
238
|
+
doc(this.db, "users", this.user.uid),
|
|
226
239
|
(doc) => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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: "
|
|
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.
|
|
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 (
|
|
516
|
+
public removeUser = async (docId: string): Promise<actionResponse> => {
|
|
470
517
|
const removedFrom = [];
|
|
471
|
-
const userRef = doc(this.db, "users",
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
526
|
-
|
|
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.
|
|
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
|
|
596
|
-
|
|
597
|
-
collectionPath
|
|
598
|
-
|
|
599
|
-
|
|
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:
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
703
|
-
.
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
.
|
|
712
|
-
|
|
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
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
if (
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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.
|
|
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
|
-
|
|
1221
|
+
docId: string,
|
|
1092
1222
|
collectionPath: string
|
|
1093
1223
|
): Promise<actionResponse> => {
|
|
1094
|
-
|
|
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/" +
|
|
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
|
-
|
|
1250
|
+
docId: string,
|
|
1119
1251
|
collectionPath: string
|
|
1120
1252
|
): Promise<actionResponse> => {
|
|
1121
|
-
|
|
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/" +
|
|
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
|
-
|
|
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/" +
|
|
1161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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.
|
|
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.
|
|
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
|
+
};
|