@edgedev/firebase 1.8.7 → 1.8.10
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 +73 -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,97 @@ staticSearch.getData("myItems", query, sort, limit);
|
|
|
863
768
|
</script>
|
|
864
769
|
```
|
|
865
770
|
|
|
771
|
+
# Run a Cloud Function
|
|
866
772
|
|
|
867
|
-
|
|
773
|
+
### edgeFirebase.runFunction('cloudFunction', {data});
|
|
868
774
|
|
|
869
|
-
|
|
775
|
+
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.
|
|
870
776
|
|
|
871
|
-
|
|
872
|
-
const response = edgeFirebase.startSnapshot("things");
|
|
873
|
-
const response = await edgeFirebase.storeDoc("myItems", {name: "John Doe"});
|
|
874
|
-
```
|
|
777
|
+
#### Parameters
|
|
875
778
|
|
|
876
|
-
|
|
779
|
+
- `functionName`: A string representing the name of the cloud function to be invoked.
|
|
780
|
+
- `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.
|
|
877
781
|
|
|
878
|
-
|
|
879
|
-
interface actionResponse {
|
|
880
|
-
success: boolean;
|
|
881
|
-
message: string;
|
|
882
|
-
meta: {}
|
|
883
|
-
}
|
|
884
|
-
```
|
|
782
|
+
#### Returns
|
|
885
783
|
|
|
886
|
-
|
|
784
|
+
A Promise that resolves to the result of the invoked cloud function.
|
|
785
|
+
|
|
786
|
+
#### Example
|
|
787
|
+
|
|
788
|
+
Suppose you have a cloud function named `sendNotification` that takes two arguments: `message` and `recipientId`.
|
|
789
|
+
|
|
790
|
+
1. First, you need to define the `sendNotification` function in your Firebase `index.js`:
|
|
887
791
|
|
|
888
792
|
```javascript
|
|
889
|
-
|
|
890
|
-
service cloud.firestore {
|
|
793
|
+
const functions = require('firebase-functions');
|
|
891
794
|
|
|
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;
|
|
795
|
+
exports.sendNotification = functions.https.onCall(async (data, context) => {
|
|
796
|
+
if (data.uid !== context.auth.uid) {
|
|
797
|
+
throw new functions.https.HttpsError('permission-denied', 'Unauthorized access');
|
|
904
798
|
}
|
|
905
799
|
|
|
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
|
-
}
|
|
800
|
+
const message = data.message;
|
|
801
|
+
const recipientId = data.recipientId;
|
|
802
|
+
const uid = data.uid; // The user's UID is automatically included in the data object
|
|
920
803
|
|
|
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
|
-
}
|
|
804
|
+
// Your notification sending logic here
|
|
951
805
|
|
|
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
|
-
}
|
|
806
|
+
return { success: true, message: 'Notification sent successfully' };
|
|
807
|
+
});
|
|
808
|
+
```
|
|
974
809
|
|
|
810
|
+
1. To call this function using the `runFunction` method, you would do the following:
|
|
975
811
|
|
|
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
|
-
);
|
|
812
|
+
```javascript
|
|
813
|
+
<script setup>
|
|
814
|
+
const edgeFirebase = ...; // Reference to the object containing the runFunction method
|
|
815
|
+
const sendNotification = async () => {
|
|
816
|
+
try {
|
|
817
|
+
const message = "Hello, User!";
|
|
818
|
+
const recipientId = "someUserId";
|
|
819
|
+
const result = await edgeFirebase.runFunction("sendNotification", { message, recipientId });
|
|
820
|
+
console.log("Notification sent successfully:", result);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.error("Error sending notification:", error);
|
|
1011
823
|
}
|
|
824
|
+
};
|
|
825
|
+
</script>
|
|
1012
826
|
|
|
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
|
-
}
|
|
827
|
+
<template>
|
|
828
|
+
<button @click="sendNotification">Send Notification</button>
|
|
829
|
+
</template>
|
|
830
|
+
```
|
|
1030
831
|
|
|
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
|
-
)
|
|
832
|
+
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
833
|
|
|
1058
|
-
|
|
834
|
+
# Responses
|
|
1059
835
|
|
|
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
|
-
}
|
|
836
|
+
Most functions will return a response that can be used.
|
|
1065
837
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}
|
|
838
|
+
```javascript
|
|
839
|
+
const response = edgeFirebase.startSnapshot("things");
|
|
840
|
+
const response = await edgeFirebase.storeDoc("myItems", {name: "John Doe"});
|
|
841
|
+
```
|
|
1071
842
|
|
|
1072
|
-
|
|
1073
|
-
return request.resource.data.roles.size() == 0 && request.resource.data.specialPermissions.size() == 0;
|
|
1074
|
-
}
|
|
843
|
+
reponse:
|
|
1075
844
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
845
|
+
```typescript
|
|
846
|
+
interface actionResponse {
|
|
847
|
+
success: boolean;
|
|
848
|
+
message: string;
|
|
849
|
+
meta: {}
|
|
850
|
+
}
|
|
851
|
+
```
|
|
1082
852
|
|
|
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
|
-
}
|
|
853
|
+
# Firestore Rules
|
|
1095
854
|
|
|
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
|
-
}
|
|
855
|
+
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"
|
|
856
|
+
|
|
857
|
+
```javascript
|
|
858
|
+
rules_version = '2';
|
|
859
|
+
// #EDGE FIREBASE RULES START
|
|
860
|
+
|
|
861
|
+
// #EDGE FIREBASE RULES END
|
|
1174
862
|
```
|
|
1175
863
|
|
|
1176
864
|
|
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;
|