@edgedev/firebase 1.8.11 → 1.9.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 +57 -0
- package/edgeFirebase.ts +36 -6
- package/package.json +1 -1
- package/src/firestore.rules +17 -10
- package/src/functions.js +85 -0
package/README.md
CHANGED
|
@@ -239,6 +239,63 @@ Calling this will generate a Microsoft Sign In Popup and register the user using
|
|
|
239
239
|
);
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
### Inviting an Existing User to Register with a New Organization or Member
|
|
243
|
+
|
|
244
|
+
To invite an existing user to register with a new organization or member's data and get the corresponding roles, use the `edgeFirebase.currentUserRegister(userRegister)` method.
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
const userRegisterData = {
|
|
248
|
+
registrationCode: "12345",
|
|
249
|
+
dynamicDocumentFieldValue: "fieldName",
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const response = await edgeFirebase.currentUserRegister(userRegisterData);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Parameters
|
|
256
|
+
|
|
257
|
+
- `userRegister` (object): An object containing the user registration data. It must include a `registrationCode` property provided by the inviting organization or member. It can also include a `dynamicDocumentFieldValue` property, which is a single string representing the name of an additional data field for registration.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
interface userRegister {
|
|
261
|
+
registrationCode: string;
|
|
262
|
+
dynamicDocumentFieldValue?: string;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### Returns
|
|
267
|
+
|
|
268
|
+
The method returns a Promise that resolves to an `actionResponse` object:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
interface actionResponse {
|
|
272
|
+
success: boolean;
|
|
273
|
+
message: string;
|
|
274
|
+
meta: {};
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Example usage:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
<script setup>
|
|
282
|
+
async function inviteExistingUser() {
|
|
283
|
+
const userRegisterData = {
|
|
284
|
+
registrationCode: "12345",
|
|
285
|
+
dynamicDocumentFieldValue: "fieldName",
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const response = await edgeFirebase.currentUserRegister(userRegisterData);
|
|
289
|
+
if (response.success) {
|
|
290
|
+
console.log("Existing user invited and registered successfully");
|
|
291
|
+
} else {
|
|
292
|
+
console.error("Error inviting and registering existing user:", response.message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
|
|
242
299
|
### Explanation of permissions
|
|
243
300
|
|
|
244
301
|
- **assign: boolean** - When a user has this permission for a collection they can assign other users to the collection and change permissions for that collection. For a user to be able run setUser, storeCollectionPermisions, storeUserRoles, removeUserRoles, storeUserSpecialPermissions, or removeUserSpecialPermissions, they must have assign access to any of the collection paths passed into those functions.
|
package/edgeFirebase.ts
CHANGED
|
@@ -126,6 +126,7 @@ interface newUser {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
interface userRegister {
|
|
129
|
+
uid?: string;
|
|
129
130
|
email?: string;
|
|
130
131
|
password?: string;
|
|
131
132
|
meta: object;
|
|
@@ -233,7 +234,7 @@ export const EdgeFirebase = class {
|
|
|
233
234
|
|
|
234
235
|
private functions = null;
|
|
235
236
|
|
|
236
|
-
public runFunction = async (functionName: string, data:
|
|
237
|
+
public runFunction = async (functionName: string, data: Record<string, unknown>) => {
|
|
237
238
|
data.uid = this.user.uid;
|
|
238
239
|
const callable = httpsCallable(this.functions, functionName);
|
|
239
240
|
return await callable(data);
|
|
@@ -462,6 +463,32 @@ export const EdgeFirebase = class {
|
|
|
462
463
|
}
|
|
463
464
|
}
|
|
464
465
|
|
|
466
|
+
public currentUserRegister = async (userRegister: userRegister): Promise<actionResponse> => {
|
|
467
|
+
if (!Object.prototype.hasOwnProperty.call(userRegister, 'registrationCode') || userRegister.registrationCode === "") {
|
|
468
|
+
return this.sendResponse({
|
|
469
|
+
success: false,
|
|
470
|
+
message: "Registration code is required.",
|
|
471
|
+
meta: {}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
// userRegister.uid = this.user.uid;
|
|
475
|
+
const result = await this.runFunction("currentUserRegister", userRegister as unknown as Record<string, unknown>);
|
|
476
|
+
const resultData = result.data as {success: boolean, message: string};
|
|
477
|
+
if (resultData.success) {
|
|
478
|
+
return this.sendResponse({
|
|
479
|
+
success: true,
|
|
480
|
+
message: "",
|
|
481
|
+
meta: {}
|
|
482
|
+
});
|
|
483
|
+
} else {
|
|
484
|
+
return this.sendResponse({
|
|
485
|
+
success: false,
|
|
486
|
+
message: resultData.message,
|
|
487
|
+
meta: {}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
465
492
|
public registerUser = async (
|
|
466
493
|
userRegister: userRegister,
|
|
467
494
|
authProvider: authProviders = "email",
|
|
@@ -632,7 +659,7 @@ export const EdgeFirebase = class {
|
|
|
632
659
|
|
|
633
660
|
public removeUser = async (docId: string): Promise<actionResponse> => {
|
|
634
661
|
const removedFrom = [];
|
|
635
|
-
const userRef = doc(this.db, "users", docId);
|
|
662
|
+
const userRef = doc(this.db, "staged-users", docId);
|
|
636
663
|
const userSnap = await getDoc(userRef);
|
|
637
664
|
if (userSnap.data().roles) {
|
|
638
665
|
for (const collectionPath in userSnap.data().roles) {
|
|
@@ -653,7 +680,7 @@ export const EdgeFirebase = class {
|
|
|
653
680
|
collectionPath.replaceAll("-", "/")
|
|
654
681
|
);
|
|
655
682
|
if (canAssign) {
|
|
656
|
-
|
|
683
|
+
await this.removeUserSpecialPermissions(
|
|
657
684
|
docId,
|
|
658
685
|
collectionPath.replaceAll("-", "/")
|
|
659
686
|
);
|
|
@@ -662,10 +689,11 @@ export const EdgeFirebase = class {
|
|
|
662
689
|
}
|
|
663
690
|
}
|
|
664
691
|
if (removedFrom.length > 0) {
|
|
692
|
+
const response = await this.runFunction("removeNonRegisteredUser", {uid: this.user.uid, docId});
|
|
665
693
|
return this.sendResponse({
|
|
666
694
|
success: true,
|
|
667
695
|
message: "",
|
|
668
|
-
meta: {}
|
|
696
|
+
meta: {response}
|
|
669
697
|
});
|
|
670
698
|
} else {
|
|
671
699
|
return this.sendResponse({
|
|
@@ -892,8 +920,8 @@ export const EdgeFirebase = class {
|
|
|
892
920
|
};
|
|
893
921
|
|
|
894
922
|
private generateUserMeta = async (userMeta: newUser): Promise<actionResponse> => {
|
|
895
|
-
const roles: role[] = userMeta.roles;
|
|
896
|
-
const specialPermissions: specialPermission[] = userMeta.specialPermissions;
|
|
923
|
+
const roles: role[] = userMeta.roles || [];
|
|
924
|
+
const specialPermissions: specialPermission[] = userMeta.specialPermissions || [];
|
|
897
925
|
delete userMeta.roles;
|
|
898
926
|
delete userMeta.specialPermissions;
|
|
899
927
|
|
|
@@ -1411,6 +1439,7 @@ export const EdgeFirebase = class {
|
|
|
1411
1439
|
}
|
|
1412
1440
|
if (canAssign) {
|
|
1413
1441
|
await updateDoc(doc(this.db, "staged-users/" + docId), {
|
|
1442
|
+
uid: this.user.uid,
|
|
1414
1443
|
collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
|
|
1415
1444
|
["roles." + collectionPath.replaceAll("/", "-")]: deleteField()
|
|
1416
1445
|
});
|
|
@@ -1440,6 +1469,7 @@ export const EdgeFirebase = class {
|
|
|
1440
1469
|
}
|
|
1441
1470
|
if (canAssign) {
|
|
1442
1471
|
await updateDoc(doc(this.db, "staged-users/" + docId), {
|
|
1472
|
+
uid: this.user.uid,
|
|
1443
1473
|
collectionPaths: arrayRemove(collectionPath.replaceAll("/", "-")),
|
|
1444
1474
|
["specialPermissions." + collectionPath.replaceAll("/", "-")]:
|
|
1445
1475
|
deleteField()
|
package/package.json
CHANGED
package/src/firestore.rules
CHANGED
|
@@ -68,20 +68,27 @@ service cloud.firestore {
|
|
|
68
68
|
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
|
|
72
|
-
request.resource.data.userId == resource.data.userId ||
|
|
71
|
+
request.auth.uid == request.resource.data.uid &&
|
|
73
72
|
(
|
|
74
|
-
resource.data.userId == "" &&
|
|
75
73
|
(
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
(
|
|
75
|
+
request.resource.data.userId == resource.data.userId ||
|
|
76
|
+
resource.data.userId == ""
|
|
77
|
+
) &&
|
|
78
|
+
(
|
|
79
|
+
request.resource.data.userId == request.auth.uid ||
|
|
80
|
+
request.resource.data.templateUserId == request.auth.uid
|
|
81
|
+
)
|
|
82
|
+
) ||
|
|
83
|
+
(
|
|
84
|
+
request.resource.data.userId == resource.data.userId &&
|
|
85
|
+
"edge-assignment-helper" in ruleHelper &&
|
|
86
|
+
permissionUpdatesCheck(user, ruleHelper, "roles") &&
|
|
87
|
+
permissionUpdatesCheck(user, ruleHelper, "specialPermssions")
|
|
78
88
|
)
|
|
79
89
|
)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
permissionUpdatesCheck(user, ruleHelper, "roles") &&
|
|
83
|
-
permissionUpdatesCheck(user, ruleHelper, "specialPermssions") &&
|
|
84
|
-
request.auth.uid == request.resource.data.uid;
|
|
90
|
+
);
|
|
91
|
+
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
|
package/src/functions.js
CHANGED
|
@@ -1,4 +1,89 @@
|
|
|
1
1
|
// START @edge/firebase functions
|
|
2
|
+
exports.removeNonRegisteredUser = functions.https.onCall(async (data, context) => {
|
|
3
|
+
if (data.uid === context.auth.uid) {
|
|
4
|
+
const stagedUser = await db.collection('staged-users').doc(data.docId).get()
|
|
5
|
+
if (stagedUser.exists) {
|
|
6
|
+
const stagedUserData = stagedUser.data()
|
|
7
|
+
|
|
8
|
+
const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
|
|
9
|
+
const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
|
|
10
|
+
const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
|
|
11
|
+
|
|
12
|
+
if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
|
|
13
|
+
await db.collection('staged-users').doc(data.docId).delete()
|
|
14
|
+
return { success: true, message: '' }
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
let message = ''
|
|
18
|
+
if (rolesExist && specialPermissionsExist) {
|
|
19
|
+
message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
|
|
20
|
+
}
|
|
21
|
+
else if (rolesExist) {
|
|
22
|
+
message = 'Cannot delete because the non-registered user still has roles assigned.'
|
|
23
|
+
}
|
|
24
|
+
else if (specialPermissionsExist) {
|
|
25
|
+
message = 'Cannot delete because the non-registered user still has special permissions assigned.'
|
|
26
|
+
}
|
|
27
|
+
else if (userIdExistsAndNotBlank) {
|
|
28
|
+
message = 'Cannot delete because the user is registered.'
|
|
29
|
+
}
|
|
30
|
+
return { success: false, message }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { success: false, message: 'Non-registered user not found.' }
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
exports.currentUserRegister = functions.https.onCall(async (data, context) => {
|
|
38
|
+
if (data.uid === context.auth.uid) {
|
|
39
|
+
const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
|
|
40
|
+
if (!stagedUser.exists) {
|
|
41
|
+
return { success: false, message: 'Registration code not found.' }
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const stagedUserData = await stagedUser.data()
|
|
45
|
+
let process = false
|
|
46
|
+
if (stagedUserData.isTemplate) {
|
|
47
|
+
process = true
|
|
48
|
+
}
|
|
49
|
+
if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
|
|
50
|
+
process = true
|
|
51
|
+
}
|
|
52
|
+
if (!process) {
|
|
53
|
+
return { success: false, message: 'Registration code not valid.' }
|
|
54
|
+
}
|
|
55
|
+
const newRoles = stagedUserData.roles || {}
|
|
56
|
+
const currentUser = await db.collection('users').doc(data.uid).get()
|
|
57
|
+
const currentUserData = await currentUser.data()
|
|
58
|
+
const currentRoles = currentUserData.roles || {}
|
|
59
|
+
const currentUserCollectionPaths = currentUserData.collectionPaths || []
|
|
60
|
+
let newRole = {}
|
|
61
|
+
if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
|
|
62
|
+
if (!data.dynamicDocumentFieldValue) {
|
|
63
|
+
return { success: false, message: 'Dynamic document field value is required.' }
|
|
64
|
+
}
|
|
65
|
+
const rootPath = stagedUserData.subCreate.rootPath
|
|
66
|
+
const newDoc = stagedUserData.subCreate.documentStructure
|
|
67
|
+
newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
|
|
68
|
+
const addedDoc = await db.collection(rootPath).add(newDoc)
|
|
69
|
+
await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
|
|
70
|
+
newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
|
|
71
|
+
}
|
|
72
|
+
const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
|
|
73
|
+
Object.values(combinedRoles).forEach((role) => {
|
|
74
|
+
if (!currentUserCollectionPaths.includes(role.collectionPath)) {
|
|
75
|
+
currentUserCollectionPaths.push(role.collectionPath)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
|
|
79
|
+
if (!stagedUserData.isTemplate) {
|
|
80
|
+
await db.collection('staged-users').doc(data.registrationCode).delete()
|
|
81
|
+
}
|
|
82
|
+
return { success: true, message: '' }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
2
87
|
exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate((change, context) => {
|
|
3
88
|
const eventId = context.eventId
|
|
4
89
|
const eventRef = db.collection('events').doc(eventId)
|