@edgedev/firebase 1.8.7 → 1.8.9
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 +71 -385
- package/edgeFirebase.ts +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ A Vue 3 / Nuxt 3 Plugin or Nuxt 3 global composable for firebase authentication
|
|
|
8
8
|
**[Firebase Authentication](#firebase-authentication)**
|
|
9
9
|
**[Firestore Basic Document Interactions](#firestore-Basic-document-interactions)**
|
|
10
10
|
**[Firestore Snapshot Listeners](#firestore-snapshot-listeners)**
|
|
11
|
-
**[Firestore Static Collection Data](#firestore-static-collection-data)**
|
|
11
|
+
**[Firestore Static Collection Data](#firestore-static-collection-data)**
|
|
12
|
+
**[Run a Cloud Function](#run-a-cloud-function)**
|
|
12
13
|
**[Await and response](#responses)**
|
|
13
14
|
**[Firestore Rules](#firestore-rules)**
|
|
14
15
|
|
|
@@ -38,6 +39,7 @@ const config = {
|
|
|
38
39
|
appId: "your-appId",
|
|
39
40
|
emulatorAuth: "", // local emlulator port populated app will be started with auth emulator
|
|
40
41
|
emulatorFirestore: "", // local emlulator port populated app will be started with firestore emulator
|
|
42
|
+
emulatorFunctions: "", // local emulator port populated app will be started with functions emulator, used to test Cloud Functions locally.
|
|
41
43
|
};
|
|
42
44
|
const isPersistant = true // If "persistence" is true, login will be saved locally, they can close their browser and when they open they will be logged in automatically. If "persistence" is false login saved only for the session.
|
|
43
45
|
const edgeFirebase = new EdgeFirebase(config, isPersistant);
|
|
@@ -68,6 +70,7 @@ app.use(eFb, {
|
|
|
68
70
|
appId: "your-appId",
|
|
69
71
|
emulatorAuth: "", // local emlulator port populated app will be started with auth emulator
|
|
70
72
|
emulatorFirestore: "", // local emlulator port populated app will be started with firestore emulator
|
|
73
|
+
emulatorFunctions: "", // local emulator port populated app will be started with functions emulator, used to test Cloud Functions locally.
|
|
71
74
|
}, isPersistant)
|
|
72
75
|
//end edgeFirebase
|
|
73
76
|
|
|
@@ -117,7 +120,7 @@ The trigger considers various scenarios such as the presence of a `userId` field
|
|
|
117
120
|
|
|
118
121
|
In essence, the `updateUser` trigger streamlines user data management by automatically synchronizing updates between the `staged-users` and `users` collections in your Firebase project and adds another layer of security.
|
|
119
122
|
|
|
120
|
-
User mangement requires setting up
|
|
123
|
+
User mangement requires setting up a firestore trigger function and helper functions in your firebase functions, these functions are automatically added to functions/index.js in your project, wrapped in "// START @edge/firebase functions" and "// END @edge/firebase functions".
|
|
121
124
|
|
|
122
125
|
```javascript
|
|
123
126
|
const functions = require('firebase-functions')
|
|
@@ -126,105 +129,7 @@ admin.initializeApp()
|
|
|
126
129
|
const db = admin.firestore()
|
|
127
130
|
|
|
128
131
|
// START @edge/firebase functions
|
|
129
|
-
|
|
130
|
-
const eventId = context.eventId
|
|
131
|
-
const eventRef = db.collection('events').doc(eventId)
|
|
132
|
-
const stagedDocId = context.params.docId
|
|
133
|
-
let newData = change.after.data()
|
|
134
|
-
const oldData = change.before.data()
|
|
135
|
-
return shouldProcess(eventRef).then((process) => {
|
|
136
|
-
if (process) {
|
|
137
|
-
// Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
|
|
138
|
-
if (newData.userId) {
|
|
139
|
-
const userRef = db.collection('users').doc(newData.userId)
|
|
140
|
-
setUser(userRef, newData, oldData, stagedDocId).then(() => {
|
|
141
|
-
return markProcessed(eventRef)
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
if (newData.templateUserId !== oldData.templateUserId) {
|
|
146
|
-
newData.isTemplate = false
|
|
147
|
-
const templateUserId = newData.templateUserId
|
|
148
|
-
newData.meta = newData.templateMeta
|
|
149
|
-
delete newData.templateMeta
|
|
150
|
-
delete newData.templateUserId
|
|
151
|
-
if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
|
|
152
|
-
const subCreate = newData.subCreate
|
|
153
|
-
delete newData.subCreate
|
|
154
|
-
db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue }).then((addedDoc) => {
|
|
155
|
-
db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id }).then(() => {
|
|
156
|
-
delete newData.dynamicDocumentFieldValue
|
|
157
|
-
const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
|
|
158
|
-
if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
|
|
159
|
-
newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
|
|
163
|
-
}
|
|
164
|
-
const newRoles = { ...newData.roles, ...newRole }
|
|
165
|
-
newData = { ...newData, roles: newRoles }
|
|
166
|
-
const stagedUserRef = db.collection('staged-users').doc(templateUserId)
|
|
167
|
-
return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
|
|
168
|
-
const userRef = db.collection('users').doc(templateUserId)
|
|
169
|
-
setUser(userRef, newData, oldData, templateUserId).then(() => {
|
|
170
|
-
return markProcessed(eventRef)
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
const stagedUserRef = db.collection('staged-users').doc(templateUserId)
|
|
178
|
-
return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
|
|
179
|
-
const userRef = db.collection('users').doc(templateUserId)
|
|
180
|
-
setUser(userRef, newData, oldData, templateUserId).then(() => {
|
|
181
|
-
return markProcessed(eventRef)
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return markProcessed(eventRef)
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
function setUser(userRef, newData, oldData, stagedDocId) {
|
|
193
|
-
return userRef.get().then((user) => {
|
|
194
|
-
let userUpdate = { meta: newData.meta, stagedDocId }
|
|
195
|
-
|
|
196
|
-
if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
|
|
197
|
-
userUpdate = { ...userUpdate, roles: newData.roles }
|
|
198
|
-
}
|
|
199
|
-
if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
|
|
200
|
-
userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (!oldData.userId) {
|
|
204
|
-
userUpdate = { ...userUpdate, userId: newData.uid }
|
|
205
|
-
}
|
|
206
|
-
console.log(userUpdate)
|
|
207
|
-
if (!user.exists) {
|
|
208
|
-
return userRef.set(userUpdate)
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
return userRef.update(userUpdate)
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function shouldProcess(eventRef) {
|
|
217
|
-
return eventRef.get().then((eventDoc) => {
|
|
218
|
-
return !eventDoc.exists || !eventDoc.data().processed
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function markProcessed(eventRef) {
|
|
223
|
-
return eventRef.set({ processed: true }).then(() => {
|
|
224
|
-
return null
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
|
|
132
|
+
.......
|
|
228
133
|
// END @edge/firebase functions
|
|
229
134
|
```
|
|
230
135
|
|
|
@@ -259,7 +164,7 @@ edgeFirebase.addUser({
|
|
|
259
164
|
}
|
|
260
165
|
],
|
|
261
166
|
meta: { firstName: "John", lastName: "Doe", age: 28 } // This is just an example of meta, it can contain any fields and any number of fields.
|
|
262
|
-
|
|
167
|
+
isTemplate: true, // Optional - Only true if setting up template for self registation
|
|
263
168
|
subCreate: {
|
|
264
169
|
rootPath: 'myItems',
|
|
265
170
|
role: 'admin',
|
|
@@ -863,314 +768,95 @@ staticSearch.getData("myItems", query, sort, limit);
|
|
|
863
768
|
</script>
|
|
864
769
|
```
|
|
865
770
|
|
|
771
|
+
# Run a Cloud Function
|
|
866
772
|
|
|
867
|
-
|
|
773
|
+
This function allows you to invoke a specified cloud function by providing its name and an optional data object. The user's UID is automatically added to the data object before the cloud function is called.
|
|
868
774
|
|
|
869
|
-
|
|
775
|
+
#### Parameters
|
|
870
776
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const response = await edgeFirebase.storeDoc("myItems", {name: "John Doe"});
|
|
874
|
-
```
|
|
777
|
+
- `functionName`: A string representing the name of the cloud function to be invoked.
|
|
778
|
+
- `data`: An optional object containing key-value pairs that will be passed to the cloud function as arguments. The user's UID is automatically included in the data object.
|
|
875
779
|
|
|
876
|
-
|
|
780
|
+
#### Returns
|
|
877
781
|
|
|
878
|
-
|
|
879
|
-
interface actionResponse {
|
|
880
|
-
success: boolean;
|
|
881
|
-
message: string;
|
|
882
|
-
meta: {}
|
|
883
|
-
}
|
|
884
|
-
```
|
|
782
|
+
A Promise that resolves to the result of the invoked cloud function.
|
|
885
783
|
|
|
886
|
-
|
|
784
|
+
#### Example
|
|
785
|
+
|
|
786
|
+
Suppose you have a cloud function named `sendNotification` that takes two arguments: `message` and `recipientId`.
|
|
787
|
+
|
|
788
|
+
1. First, you need to define the `sendNotification` function in your Firebase `index.js`:
|
|
887
789
|
|
|
888
790
|
```javascript
|
|
889
|
-
|
|
890
|
-
service cloud.firestore {
|
|
791
|
+
const functions = require('firebase-functions');
|
|
891
792
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
allow update: if false;
|
|
896
|
-
allow delete: if false;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
match /databases/{database}/documents/rule-helpers/{helper} {
|
|
900
|
-
allow read: if false;
|
|
901
|
-
allow create: if request.auth.uid == request.resource.data.uid;
|
|
902
|
-
allow update: if request.auth.uid == request.resource.data.uid;
|
|
903
|
-
allow delete: if false;
|
|
793
|
+
exports.sendNotification = functions.https.onCall(async (data, context) => {
|
|
794
|
+
if (data.uid !== context.auth.uid) {
|
|
795
|
+
throw new functions.https.HttpsError('permission-denied', 'Unauthorized access');
|
|
904
796
|
}
|
|
905
797
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
(
|
|
910
|
-
"userId" in resource.data &&
|
|
911
|
-
resource.data.userId == request.auth.uid
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
allow read: if readSelf();
|
|
916
|
-
allow create: if false;
|
|
917
|
-
allow update: if false;
|
|
918
|
-
allow delete: if readSelf();
|
|
919
|
-
}
|
|
798
|
+
const message = data.message;
|
|
799
|
+
const recipientId = data.recipientId;
|
|
800
|
+
const uid = data.uid; // The user's UID is automatically included in the data object
|
|
920
801
|
|
|
921
|
-
|
|
922
|
-
// TODO: these rules need tested.
|
|
923
|
-
function getRolePermission(role, collection, permissionCheck) {
|
|
924
|
-
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
925
|
-
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
926
|
-
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
927
|
-
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
928
|
-
}
|
|
929
|
-
function canAssign() {
|
|
930
|
-
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
931
|
-
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data['edge-assignment-helper'];
|
|
932
|
-
return collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$") &&
|
|
933
|
-
(
|
|
934
|
-
"specialPermissions" in user &&
|
|
935
|
-
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
936
|
-
"assign" in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
937
|
-
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath]["assign"]
|
|
938
|
-
) ||
|
|
939
|
-
(
|
|
940
|
-
"roles" in user &&
|
|
941
|
-
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
942
|
-
"role" in user.roles[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
943
|
-
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, collectionPath, "assign")
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
allow read: if request.auth != null; // All signed in users can read collection-data
|
|
947
|
-
allow create: if canAssign();
|
|
948
|
-
allow update: if canAssign();
|
|
949
|
-
allow delete: if canAssign();
|
|
950
|
-
}
|
|
802
|
+
// Your notification sending logic here
|
|
951
803
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
956
|
-
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
957
|
-
|
|
958
|
-
return (
|
|
959
|
-
resource == null ||
|
|
960
|
-
request.resource.data.userId == resource.data.userId ||
|
|
961
|
-
(
|
|
962
|
-
resource.data.userId == "" &&
|
|
963
|
-
(
|
|
964
|
-
request.resource.data.userId == request.auth.uid ||
|
|
965
|
-
request.resource.data.templateUserId == request.auth.uid
|
|
966
|
-
)
|
|
967
|
-
)
|
|
968
|
-
) &&
|
|
969
|
-
"edge-assignment-helper" in ruleHelper &&
|
|
970
|
-
permissionUpdatesCheck(user, ruleHelper, "roles") &&
|
|
971
|
-
permissionUpdatesCheck(user, ruleHelper, "specialPermssions") &&
|
|
972
|
-
request.auth.uid == request.resource.data.uid;
|
|
973
|
-
}
|
|
804
|
+
return { success: true, message: 'Notification sent successfully' };
|
|
805
|
+
});
|
|
806
|
+
```
|
|
974
807
|
|
|
808
|
+
1. To call this function using the `runFunction` method, you would do the following:
|
|
975
809
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
function permissionCheck(permissionType, user, ruleHelper) {
|
|
988
|
-
let lastPathUpdated = ruleHelper["edge-assignment-helper"].fullPath;
|
|
989
|
-
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
990
|
-
return request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 0 ||
|
|
991
|
-
(
|
|
992
|
-
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 1 &&
|
|
993
|
-
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys() == [lastPathUpdated].toSet() &&
|
|
994
|
-
(
|
|
995
|
-
permissionCheckPath == "-" ||
|
|
996
|
-
lastPathUpdated.matches("^" + permissionCheckPath + ".*$")
|
|
997
|
-
) &&
|
|
998
|
-
(
|
|
999
|
-
(
|
|
1000
|
-
"roles" in user &&
|
|
1001
|
-
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
1002
|
-
) ||
|
|
1003
|
-
(
|
|
1004
|
-
"specialPermissions" in user &&
|
|
1005
|
-
permissionCheckPath in user.specialPermissions &&
|
|
1006
|
-
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
1007
|
-
user.specialPermissions[permissionCheckPath]["assign"]
|
|
1008
|
-
)
|
|
1009
|
-
)
|
|
1010
|
-
);
|
|
810
|
+
```javascript
|
|
811
|
+
<script setup>
|
|
812
|
+
const edgeFirebase = ...; // Reference to the object containing the runFunction method
|
|
813
|
+
const sendNotification = async () => {
|
|
814
|
+
try {
|
|
815
|
+
const message = "Hello, User!";
|
|
816
|
+
const recipientId = "someUserId";
|
|
817
|
+
const result = await edgeFirebase.runFunction("sendNotification", { message, recipientId });
|
|
818
|
+
console.log("Notification sent successfully:", result);
|
|
819
|
+
} catch (error) {
|
|
820
|
+
console.error("Error sending notification:", error);
|
|
1011
821
|
}
|
|
822
|
+
};
|
|
823
|
+
</script>
|
|
1012
824
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
(
|
|
1018
|
-
"roles" in user &&
|
|
1019
|
-
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.roles &&
|
|
1020
|
-
getRolePermission(user.roles[ruleHelper["edge-assignment-helper"].permissionCheckPath].role, ruleHelper["edge-assignment-helper"].permissionCheckPath, 'assign')
|
|
1021
|
-
) ||
|
|
1022
|
-
(
|
|
1023
|
-
"specialPermissions" in user &&
|
|
1024
|
-
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.specialPermissions &&
|
|
1025
|
-
"assign" in user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath] &&
|
|
1026
|
-
user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath]["assign"]
|
|
1027
|
-
)
|
|
1028
|
-
)
|
|
1029
|
-
}
|
|
825
|
+
<template>
|
|
826
|
+
<button @click="sendNotification">Send Notification</button>
|
|
827
|
+
</template>
|
|
828
|
+
```
|
|
1030
829
|
|
|
1031
|
-
|
|
1032
|
-
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
1033
|
-
return (
|
|
1034
|
-
!("subCreate" in request.resource.data) ||
|
|
1035
|
-
(
|
|
1036
|
-
"subCreate" in request.resource.data &&
|
|
1037
|
-
request.resource.data.subCreate.keys().size() == 0
|
|
1038
|
-
)
|
|
1039
|
-
)||
|
|
1040
|
-
(
|
|
1041
|
-
permissionCheckPath == "-" ||
|
|
1042
|
-
request.resource.data.subCreate.rootPath.matches("^" + permissionCheckPath + ".*$")
|
|
1043
|
-
) &&
|
|
1044
|
-
(
|
|
1045
|
-
(
|
|
1046
|
-
"roles" in user &&
|
|
1047
|
-
permissionCheckPath in user.roles &&
|
|
1048
|
-
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
1049
|
-
) ||
|
|
1050
|
-
(
|
|
1051
|
-
"specialPermissions" in user &&
|
|
1052
|
-
permissionCheckPath in user.specialPermissions &&
|
|
1053
|
-
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
1054
|
-
user.specialPermissions[permissionCheckPath]["assign"]
|
|
1055
|
-
)
|
|
1056
|
-
)
|
|
830
|
+
In this example, clicking the "Send Notification" button will invoke the `sendNotification` cloud function with the specified `message` and `recipientId`. The result of the function will be logged to the console. The cloud function checks if the provided `data.uid` matches the authenticated user's UID (`context.auth.uid`) for security purposes.
|
|
1057
831
|
|
|
1058
|
-
|
|
832
|
+
# Responses
|
|
1059
833
|
|
|
1060
|
-
|
|
1061
|
-
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
1062
|
-
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
1063
|
-
return canAssign(user, ruleHelper);
|
|
1064
|
-
}
|
|
834
|
+
Most functions will return a response that can be used.
|
|
1065
835
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}
|
|
836
|
+
```javascript
|
|
837
|
+
const response = edgeFirebase.startSnapshot("things");
|
|
838
|
+
const response = await edgeFirebase.storeDoc("myItems", {name: "John Doe"});
|
|
839
|
+
```
|
|
1071
840
|
|
|
1072
|
-
|
|
1073
|
-
return request.resource.data.roles.size() == 0 && request.resource.data.specialPermissions.size() == 0;
|
|
1074
|
-
}
|
|
841
|
+
reponse:
|
|
1075
842
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
843
|
+
```typescript
|
|
844
|
+
interface actionResponse {
|
|
845
|
+
success: boolean;
|
|
846
|
+
message: string;
|
|
847
|
+
meta: {}
|
|
848
|
+
}
|
|
849
|
+
```
|
|
1082
850
|
|
|
1083
|
-
|
|
1084
|
-
return resource == null ||
|
|
1085
|
-
("userId" in resource.data && resource.data.userId == "") ||
|
|
1086
|
-
("userId" in resource.data && resource.data.userId == request.auth.uid) ||
|
|
1087
|
-
canAssign(get(/databases/$(database)/documents/users/$(request.auth.uid)).data, get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data);
|
|
1088
|
-
}
|
|
1089
|
-
allow get: if canGet();
|
|
1090
|
-
allow list: if canList();
|
|
1091
|
-
allow create: if canCreate();
|
|
1092
|
-
allow update: if canUpdate();
|
|
1093
|
-
allow delete: if request.auth.uid == resource.data.userId // TODO: if isTemplate is true... can delete... if assign permission
|
|
1094
|
-
}
|
|
851
|
+
# Firestore Rules
|
|
1095
852
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
function checkPermission(collectionPath, permissionCheck) {
|
|
1104
|
-
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
1105
|
-
let skipPaths = ["collection-data", "users", "staged-users", "events", "rule-helpers"];
|
|
1106
|
-
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
1107
|
-
return !(collectionPath in skipPaths) &&
|
|
1108
|
-
request.auth != null &&
|
|
1109
|
-
collectionPath in ruleHelper &&
|
|
1110
|
-
"permissionCheckPath" in ruleHelper[collectionPath] &&
|
|
1111
|
-
(
|
|
1112
|
-
ruleHelper[collectionPath].permissionCheckPath == "-" ||
|
|
1113
|
-
collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$")
|
|
1114
|
-
) &&
|
|
1115
|
-
(
|
|
1116
|
-
(
|
|
1117
|
-
"roles" in user &&
|
|
1118
|
-
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
1119
|
-
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, ruleHelper[collectionPath].permissionCheckPath, permissionCheck)
|
|
1120
|
-
) ||
|
|
1121
|
-
(
|
|
1122
|
-
"specialPermissions" in user &&
|
|
1123
|
-
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
1124
|
-
permissionCheck in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
1125
|
-
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath][permissionCheck]
|
|
1126
|
-
)
|
|
1127
|
-
);
|
|
1128
|
-
}
|
|
1129
|
-
match /{seg2} {
|
|
1130
|
-
allow get: if checkPermission(seg1 + "-" + seg2, "read");
|
|
1131
|
-
allow list: if checkPermission(seg1, "read");
|
|
1132
|
-
allow create: if checkPermission(seg1, "write");
|
|
1133
|
-
allow update: if checkPermission(seg1 + "-" + seg2, "write");
|
|
1134
|
-
allow delete: if checkPermission(seg1, "delete");
|
|
1135
|
-
match /{seg3} {
|
|
1136
|
-
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
1137
|
-
allow list: if checkPermission(seg1 + "-" + seg2, "read");
|
|
1138
|
-
allow create: if checkPermission(seg1 + "-" + seg2, "write");
|
|
1139
|
-
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
1140
|
-
allow delete: if checkPermission(seg1 + "-" + seg2, "delete");
|
|
1141
|
-
match /{seg4} {
|
|
1142
|
-
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
1143
|
-
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
1144
|
-
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
1145
|
-
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
1146
|
-
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "delete");
|
|
1147
|
-
|
|
1148
|
-
match /{seg5} {
|
|
1149
|
-
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
1150
|
-
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
1151
|
-
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
1152
|
-
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
1153
|
-
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "delete");
|
|
1154
|
-
match /{seg6} {
|
|
1155
|
-
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
1156
|
-
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
1157
|
-
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
1158
|
-
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
1159
|
-
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "delete");
|
|
1160
|
-
match /{seg7} {
|
|
1161
|
-
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6 + "-" + seg7, "read");
|
|
1162
|
-
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
1163
|
-
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
1164
|
-
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6 + "-" + seg7, "write");
|
|
1165
|
-
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "delete");
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
853
|
+
Firestore rules are automatically written to your project in the firestore.rules file the are wrapped in: "// #EDGE FIREBASE RULES START" and "// #EDGE FIREBASE RULES END"
|
|
854
|
+
|
|
855
|
+
```javascript
|
|
856
|
+
rules_version = '2';
|
|
857
|
+
// #EDGE FIREBASE RULES START
|
|
858
|
+
|
|
859
|
+
// #EDGE FIREBASE RULES END
|
|
1174
860
|
```
|
|
1175
861
|
|
|
1176
862
|
|
package/edgeFirebase.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
signInWithPopup,
|
|
49
49
|
} from "firebase/auth";
|
|
50
50
|
|
|
51
|
+
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
|
|
51
52
|
|
|
52
53
|
import { getAnalytics, logEvent } from "firebase/analytics";
|
|
53
54
|
|
|
@@ -163,6 +164,7 @@ interface firebaseConfig {
|
|
|
163
164
|
emulatorAuth?: string;
|
|
164
165
|
measurementId?: string;
|
|
165
166
|
emulatorFirestore?: string;
|
|
167
|
+
emulatorFunctions?: string;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
interface actionResponse {
|
|
@@ -187,7 +189,8 @@ export const EdgeFirebase = class {
|
|
|
187
189
|
appId: "",
|
|
188
190
|
measurementId: "",
|
|
189
191
|
emulatorAuth: "",
|
|
190
|
-
emulatorFirestore: ""
|
|
192
|
+
emulatorFirestore: "",
|
|
193
|
+
emulatorFunctions: ""
|
|
191
194
|
},
|
|
192
195
|
isPersistant: false
|
|
193
196
|
) {
|
|
@@ -212,6 +215,10 @@ export const EdgeFirebase = class {
|
|
|
212
215
|
this.anaytics = getAnalytics(this.app);
|
|
213
216
|
}
|
|
214
217
|
|
|
218
|
+
this.functions = getFunctions(this.app);
|
|
219
|
+
if (this.firebaseConfig.emulatorFunctions) {
|
|
220
|
+
connectFunctionsEmulator(this.functions, "localhost", this.firebaseConfig.emulatorFunctions)
|
|
221
|
+
}
|
|
215
222
|
this.setOnAuthStateChanged();
|
|
216
223
|
}
|
|
217
224
|
|
|
@@ -223,6 +230,14 @@ export const EdgeFirebase = class {
|
|
|
223
230
|
|
|
224
231
|
private anaytics = null;
|
|
225
232
|
|
|
233
|
+
private functions = null;
|
|
234
|
+
|
|
235
|
+
public runFunction = async (functionName: string, data: { [key: string]: unknown }) => {
|
|
236
|
+
data.uid = this.user.uid;
|
|
237
|
+
const callable = httpsCallable(this.functions, functionName);
|
|
238
|
+
return await callable(data);
|
|
239
|
+
};
|
|
240
|
+
|
|
226
241
|
public logAnalyticsEvent = (eventName: string, eventParams: object = {}) => {
|
|
227
242
|
if (this.anaytics) {
|
|
228
243
|
logEvent(this.anaytics, eventName, eventParams);
|
|
@@ -399,7 +414,7 @@ export const EdgeFirebase = class {
|
|
|
399
414
|
return;
|
|
400
415
|
}
|
|
401
416
|
console.log(result.user.uid);
|
|
402
|
-
const userRef = doc(this.db, "users", result.user.uid);
|
|
417
|
+
const userRef = doc(this.db, "staged-users", result.user.uid);
|
|
403
418
|
const userSnap = await getDoc(userRef);
|
|
404
419
|
if (!userSnap.exists()) {
|
|
405
420
|
this.user.logInError = true;
|