@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/README.md
CHANGED
|
@@ -9,7 +9,8 @@ A Vue 3 / Nuxt 3 Plugin or Nuxt 3 global composable for firebase authentication
|
|
|
9
9
|
**[Firestore Basic Document Interactions](#firestore-Basic-document-interactions)**
|
|
10
10
|
**[Firestore Snapshot Listeners](#firestore-snapshot-listeners)**
|
|
11
11
|
**[Firestore Static Collection Data](#firestore-static-collection-data)**
|
|
12
|
-
**[Await and response](#
|
|
12
|
+
**[Await and response](#responses)**
|
|
13
|
+
**[Firestore Rules](#firestore-rules)**
|
|
13
14
|
|
|
14
15
|
# Installation
|
|
15
16
|
|
|
@@ -104,17 +105,140 @@ const edgeFirebase = inject("edgeFirebase");
|
|
|
104
105
|
</script>
|
|
105
106
|
```
|
|
106
107
|
|
|
108
|
+
### Firebase Trigger function.
|
|
109
|
+
|
|
110
|
+
User mangement requires setting up in this firestore trigger function and helper functions in your firebase functions:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const functions = require('firebase-functions')
|
|
114
|
+
const admin = require('firebase-admin')
|
|
115
|
+
admin.initializeApp()
|
|
116
|
+
const db = admin.firestore()
|
|
117
|
+
|
|
118
|
+
// START @edge/firebase functions
|
|
119
|
+
exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate((change, context) => {
|
|
120
|
+
const eventId = context.eventId
|
|
121
|
+
const eventRef = db.collection('events').doc(eventId)
|
|
122
|
+
const stagedDocId = context.params.docId
|
|
123
|
+
let newData = change.after.data()
|
|
124
|
+
const oldData = change.before.data()
|
|
125
|
+
return shouldProcess(eventRef).then((process) => {
|
|
126
|
+
if (process) {
|
|
127
|
+
// Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
|
|
128
|
+
if (newData.userId) {
|
|
129
|
+
const userRef = db.collection('users').doc(newData.userId)
|
|
130
|
+
setUser(userRef, newData, oldData, stagedDocId).then(() => {
|
|
131
|
+
return markProcessed(eventRef)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
if (newData.templateUserId) {
|
|
136
|
+
newData.isTemplate = false
|
|
137
|
+
const templateUserId = newData.templateUserId
|
|
138
|
+
newData.meta = newData.templateMeta
|
|
139
|
+
delete newData.templateMeta
|
|
140
|
+
delete newData.templateUserId
|
|
141
|
+
if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
|
|
142
|
+
const subCreate = newData.subCreate
|
|
143
|
+
delete newData.subCreate
|
|
144
|
+
db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue }).then((addedDoc) => {
|
|
145
|
+
db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id }).then(() => {
|
|
146
|
+
delete newData.dynamicDocumentFieldValue
|
|
147
|
+
const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
|
|
148
|
+
if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
|
|
149
|
+
newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
|
|
153
|
+
}
|
|
154
|
+
const newRoles = { ...newData.roles, ...newRole }
|
|
155
|
+
newData = { ...newData, roles: newRoles }
|
|
156
|
+
const stagedUserRef = db.collection('staged-users').doc(templateUserId)
|
|
157
|
+
return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
|
|
158
|
+
const userRef = db.collection('users').doc(templateUserId)
|
|
159
|
+
setUser(userRef, newData, oldData, templateUserId).then(() => {
|
|
160
|
+
return markProcessed(eventRef)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const stagedUserRef = db.collection('staged-users').doc(templateUserId)
|
|
168
|
+
return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
|
|
169
|
+
const userRef = db.collection('users').doc(templateUserId)
|
|
170
|
+
setUser(userRef, newData, oldData, templateUserId).then(() => {
|
|
171
|
+
return markProcessed(eventRef)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return markProcessed(eventRef)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
function setUser(userRef, newData, oldData, stagedDocId) {
|
|
183
|
+
// IT's OK If "users" doesn't match exactly matched "staged-users" because this is only preventing
|
|
184
|
+
// writing from outside the @edgdev/firebase functions, so discrepancies will be rare since
|
|
185
|
+
// the package will prevent before it gets this far.
|
|
186
|
+
return userRef.get().then((user) => {
|
|
187
|
+
let userUpdate = { meta: newData.meta, stagedDocId }
|
|
188
|
+
|
|
189
|
+
if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
|
|
190
|
+
userUpdate = { ...userUpdate, roles: newData.roles }
|
|
191
|
+
}
|
|
192
|
+
if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
|
|
193
|
+
userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!oldData.userId) {
|
|
197
|
+
userUpdate = { ...userUpdate, userId: newData.uid }
|
|
198
|
+
}
|
|
199
|
+
console.log(userUpdate)
|
|
200
|
+
if (!user.exists) {
|
|
201
|
+
return userRef.set(userUpdate)
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return userRef.update(userUpdate)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function shouldProcess(eventRef) {
|
|
210
|
+
return eventRef.get().then((eventDoc) => {
|
|
211
|
+
return !eventDoc.exists || !eventDoc.data().processed
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function markProcessed(eventRef) {
|
|
216
|
+
return eventRef.set({ processed: true }).then(() => {
|
|
217
|
+
return null
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// END @edge/firebase functions
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### To make sure your project is secure, install the firestore rules document at the end this documentation.
|
|
225
|
+
|
|
107
226
|
# User Management and Collection Permissions
|
|
108
227
|
|
|
109
228
|
### Adding a User
|
|
110
229
|
|
|
111
|
-
Users must be added before
|
|
230
|
+
Users or "Template Users" must be added before someone can register with a login and password (the first user in the project will need to be added manual, see the section below "Root permissions and first user"). When adding a user you can pass role and/or special permissions and user meta data. For more explanations on role and special permssions, see below.
|
|
231
|
+
|
|
232
|
+
Adding a user creates a document for them in the collection "staged-users". The docId of this documment is the used as a registration code and must be passed when using "registerUser" using the "registrationCode" variable.
|
|
233
|
+
|
|
234
|
+
The collection "staged-users" is a staging zone for all modifications and severs to sanitize the actual users in the "users" collection. Once a user is registered their staged-user is linked to their "users" user. Genreally speaking the users in the "users" collection should not be modified. In fact, if you adopt the firestore rules shown in this document direct modification of users in the "users" collection is not allowed. All user related functions in this package (editing of meta, setting rules and special permssions, listing of users) are done on the "staged-users" collection.
|
|
235
|
+
|
|
236
|
+
To bypass adding users and allow "self registration". You can add a user that is a "Template User". By setting the field "template" = true. For a template user you can also set up dynamic document generation and assigning of the registered user to that document with a specified role, but setting "subCreate". Then when registering the user you can pass a "dynamicDocumentFieldValue" variable. In the example below, if on registration you passed: dynamicDocumentFieldValue = "My New Organization", a document would be created under myItems that would look like this: {name: "My New Organization"}. The user would also be assigned as an admin to that newly created document. If your project is going to be completly self registration, you can can create a "Template User" and hard code that registation id into your registation process.
|
|
112
237
|
|
|
113
238
|
How to add a user:
|
|
114
239
|
|
|
115
240
|
```javascript
|
|
116
|
-
edgeFirebase.
|
|
117
|
-
email: "user@edgemarketingdesign.com",
|
|
241
|
+
edgeFirebase.addUser({
|
|
118
242
|
roles: [
|
|
119
243
|
{
|
|
120
244
|
collectionPath: "myItems/subitems/things",
|
|
@@ -128,9 +252,35 @@ edgeFirebase.setUser({
|
|
|
128
252
|
}
|
|
129
253
|
],
|
|
130
254
|
meta: { firstName: "John", lastName: "Doe", age: 28 } // This is just an example of meta, it can contain any fields and any number of fields.
|
|
255
|
+
template: true, // Optional - Only true if setting up template for self registation
|
|
256
|
+
subCreate: {
|
|
257
|
+
rootPath: 'myItems',
|
|
258
|
+
role: 'admin',
|
|
259
|
+
dynamicDocumentField: 'name',
|
|
260
|
+
documentStructure: {
|
|
261
|
+
name: '',
|
|
262
|
+
},
|
|
263
|
+
}
|
|
131
264
|
});
|
|
132
265
|
```
|
|
133
266
|
|
|
267
|
+
```typescript
|
|
268
|
+
interface newUser {
|
|
269
|
+
roles: role[];
|
|
270
|
+
specialPermissions: specialPermission[];
|
|
271
|
+
meta: object;
|
|
272
|
+
isTemplate?: boolean;
|
|
273
|
+
subCreate?: {
|
|
274
|
+
rootPath: string, // This must be a collection path (odd number of segments) since a document will be created and assigned to ther user here.
|
|
275
|
+
role: string, // must be admin, editor, writer, user
|
|
276
|
+
dynamicDocumentField: string, // This is the field in the document that will be set by the value of "dynamicDocumentFieldValue" passed during registration, like "name"
|
|
277
|
+
documentStructure: {
|
|
278
|
+
[key: string]: any
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
134
284
|
|
|
135
285
|
|
|
136
286
|
### Register User
|
|
@@ -145,9 +295,21 @@ After someoene has been added as a user they will need to "self register" to beg
|
|
|
145
295
|
firstName: "John",
|
|
146
296
|
lastName: "Doe"
|
|
147
297
|
} // This is just an example of meta, it can contain any fields and any number of fields.
|
|
298
|
+
registrationCode: (document id) // This is the document id of either an added user or a template user, when using a template you can simply hardcode the registrationCode of the remplate to allow self registration.
|
|
299
|
+
dynamicDocumentFieldValue: "" // Optional - See explaintion above about self registration and dynamic collectionPath for user roles.
|
|
148
300
|
});
|
|
149
301
|
```
|
|
150
302
|
|
|
303
|
+
```typescript
|
|
304
|
+
interface userRegister {
|
|
305
|
+
email: string;
|
|
306
|
+
password: string;
|
|
307
|
+
meta: object;
|
|
308
|
+
registrationCode: string;
|
|
309
|
+
dynamicDocumentFieldValue?: string;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
151
313
|
|
|
152
314
|
|
|
153
315
|
### Explanation of permissions
|
|
@@ -198,7 +360,7 @@ How to assign a user a role for a collection:
|
|
|
198
360
|
|
|
199
361
|
```javascript
|
|
200
362
|
edgeFirebase.storeUserRoles(
|
|
201
|
-
|
|
363
|
+
docId, //Document ID of user in staged-users collection.
|
|
202
364
|
"myItems/subitems/things",
|
|
203
365
|
"admin"
|
|
204
366
|
);
|
|
@@ -208,7 +370,7 @@ Remove a role from a user for a collection:
|
|
|
208
370
|
|
|
209
371
|
```javascript
|
|
210
372
|
edgeFirebase.removeUserRoles(
|
|
211
|
-
|
|
373
|
+
docId, //Document ID of user in staged-users collection.
|
|
212
374
|
"myItems/subitems/things"
|
|
213
375
|
);
|
|
214
376
|
```
|
|
@@ -228,7 +390,7 @@ If you want to give a user a unique set of permissions for a collection that doe
|
|
|
228
390
|
|
|
229
391
|
```javascript
|
|
230
392
|
edgeFirebase.storeUserSpecialPermissions(
|
|
231
|
-
|
|
393
|
+
docId, //Document ID of user in staged-users collection.
|
|
232
394
|
"myItems/subitems/things",
|
|
233
395
|
{
|
|
234
396
|
assign: false,
|
|
@@ -243,7 +405,7 @@ Remove user special permissions:
|
|
|
243
405
|
|
|
244
406
|
```javascript
|
|
245
407
|
edgeFirebase.removeUserSpecialPermissions(
|
|
246
|
-
|
|
408
|
+
docId, //Document ID of user in staged-users collection.
|
|
247
409
|
"myItems/subitems/things"
|
|
248
410
|
);
|
|
249
411
|
```
|
|
@@ -255,14 +417,14 @@ Remove user special permissions:
|
|
|
255
417
|
The remove user function doesn't actually delete the user completely from the system but instead removes all roles and special permissions that the user running the function has assign access for. In this way the user is "removed" as far as the "assigning user" is concerned but the user will remain a user for collections that the "assign user" doesn't have access to.
|
|
256
418
|
|
|
257
419
|
```javascript
|
|
258
|
-
edgeFirebase.removeUser(
|
|
420
|
+
edgeFirebase.removeUser(docId);
|
|
259
421
|
```
|
|
260
422
|
|
|
261
423
|
|
|
262
424
|
|
|
263
425
|
### Users Snapshot Data
|
|
264
426
|
|
|
265
|
-
This will create a reactive object (state.users) that contains the members of the collection passed to the snapshot if the user running the function has assign access for, it will be a listed index by
|
|
427
|
+
This will create a reactive object (state.users) that contains the members of the collection passed to the snapshot if the user running the function has assign access for, it will be a listed index by docId.
|
|
266
428
|
|
|
267
429
|
```javascript
|
|
268
430
|
edgeFirebase.startUsersSnapshot("myItems");
|
|
@@ -277,27 +439,19 @@ edgeFirebase.stopUsersSnapshot();
|
|
|
277
439
|
<template>
|
|
278
440
|
<div>
|
|
279
441
|
<div v-for="user in edgeFirebase.state.users" :key="item">
|
|
280
|
-
{{ user.
|
|
442
|
+
{{ user.meta.name }}
|
|
281
443
|
</div>
|
|
282
444
|
</div>
|
|
283
445
|
</template>
|
|
284
446
|
```
|
|
285
447
|
|
|
286
|
-
```typescript
|
|
287
|
-
interface usersByEmail {
|
|
288
|
-
[email: string]: [user];
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
448
|
```typescript
|
|
293
449
|
interface user {
|
|
294
|
-
|
|
450
|
+
docId: string;
|
|
295
451
|
roles: role[];
|
|
296
452
|
specialPermissions: specialPermission[];
|
|
297
453
|
userId: string;
|
|
298
|
-
docId: string;
|
|
299
454
|
uid: string;
|
|
300
|
-
last_updated: Date;
|
|
301
455
|
}
|
|
302
456
|
```
|
|
303
457
|
|
|
@@ -594,6 +748,7 @@ Most functions will return a response that can be used.
|
|
|
594
748
|
|
|
595
749
|
```javascript
|
|
596
750
|
const response = edgeFirebase.startSnapshot("things");
|
|
751
|
+
const response = await edgeFirebase.storeDoc("myItems", {name: "John Doe"});
|
|
597
752
|
```
|
|
598
753
|
|
|
599
754
|
reponse:
|
|
@@ -606,6 +761,296 @@ interface actionResponse {
|
|
|
606
761
|
}
|
|
607
762
|
```
|
|
608
763
|
|
|
764
|
+
# Firestore Rules
|
|
765
|
+
|
|
766
|
+
```javascript
|
|
767
|
+
rules_version = '2';
|
|
768
|
+
service cloud.firestore {
|
|
769
|
+
|
|
770
|
+
match /databases/{database}/documents/events/{event} {
|
|
771
|
+
allow read: if false;
|
|
772
|
+
allow create: if false;
|
|
773
|
+
allow update: if false;
|
|
774
|
+
allow delete: if false;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
match /databases/{database}/documents/rule-helpers/{helper} {
|
|
778
|
+
allow read: if false;
|
|
779
|
+
allow create: if request.auth.uid == request.resource.data.uid;
|
|
780
|
+
allow update: if request.auth.uid == request.resource.data.uid;
|
|
781
|
+
allow delete: if false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
match /databases/{database}/documents/users/{user} {
|
|
785
|
+
function readSelf() {
|
|
786
|
+
return resource == null ||
|
|
787
|
+
(
|
|
788
|
+
"userId" in resource.data &&
|
|
789
|
+
resource.data.userId == request.auth.uid
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
allow read: if readSelf();
|
|
794
|
+
allow create: if false;
|
|
795
|
+
allow update: if false;
|
|
796
|
+
allow delete: if false;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
match /databases/{database}/documents/collection-data/{collectionPath} {
|
|
800
|
+
// TODO: these rules need tested.
|
|
801
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
802
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
803
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
804
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
805
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
806
|
+
}
|
|
807
|
+
function canAssign() {
|
|
808
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
809
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data['edge-assignment-helper'];
|
|
810
|
+
return collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$") &&
|
|
811
|
+
(
|
|
812
|
+
"specialPermissions" in user &&
|
|
813
|
+
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
814
|
+
"assign" in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
815
|
+
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath]["assign"]
|
|
816
|
+
) ||
|
|
817
|
+
(
|
|
818
|
+
"roles" in user &&
|
|
819
|
+
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
820
|
+
"role" in user.roles[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
821
|
+
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, collectionPath, "assign")
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
allow read: if request.auth != null; // All signed in users can read collection-data
|
|
825
|
+
allow create: if canAssign();
|
|
826
|
+
allow update: if canAssign();
|
|
827
|
+
allow delete: if canAssign();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
match /databases/{database}/documents/staged-users/{user} {
|
|
831
|
+
|
|
832
|
+
function canUpdate() {
|
|
833
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
834
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
835
|
+
|
|
836
|
+
return (
|
|
837
|
+
resource == null ||
|
|
838
|
+
request.resource.data.userId == resource.data.userId ||
|
|
839
|
+
(
|
|
840
|
+
resource.data.userId == "" &&
|
|
841
|
+
(
|
|
842
|
+
request.resource.data.userId == request.auth.uid ||
|
|
843
|
+
request.resource.data.templateUserId == request.auth.uid
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
) &&
|
|
847
|
+
"edge-assignment-helper" in ruleHelper &&
|
|
848
|
+
permissionUpdatesCheck(user, ruleHelper, "roles") &&
|
|
849
|
+
permissionUpdatesCheck(user, ruleHelper, "specialPermssions") &&
|
|
850
|
+
request.auth.uid == request.resource.data.uid;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
function permissionUpdatesCheck(user, ruleHelper, permissionType) {
|
|
855
|
+
return !(permissionType in request.resource.data) ||
|
|
856
|
+
(
|
|
857
|
+
resource.data.userId == request.auth.uid &&
|
|
858
|
+
request.resource.data[permissionType].keys().hasOnly(resource.data[permissionType].keys())
|
|
859
|
+
) ||
|
|
860
|
+
(
|
|
861
|
+
resource.data.userId != request.auth.uid &&
|
|
862
|
+
permissionCheck(permissionType, user, ruleHelper)
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
function permissionCheck(permissionType, user, ruleHelper) {
|
|
866
|
+
let lastPathUpdated = ruleHelper["edge-assignment-helper"].fullPath;
|
|
867
|
+
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
868
|
+
return request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 0 ||
|
|
869
|
+
(
|
|
870
|
+
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 1 &&
|
|
871
|
+
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys() == [lastPathUpdated].toSet() &&
|
|
872
|
+
(
|
|
873
|
+
permissionCheckPath == "-" ||
|
|
874
|
+
lastPathUpdated.matches("^" + permissionCheckPath + ".*$")
|
|
875
|
+
) &&
|
|
876
|
+
(
|
|
877
|
+
(
|
|
878
|
+
"roles" in user &&
|
|
879
|
+
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
880
|
+
) ||
|
|
881
|
+
(
|
|
882
|
+
"specialPermissions" in user &&
|
|
883
|
+
permissionCheckPath in user.specialPermissions &&
|
|
884
|
+
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
885
|
+
user.specialPermissions[permissionCheckPath]["assign"]
|
|
886
|
+
)
|
|
887
|
+
)
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function canAssign(user, ruleHelper) {
|
|
892
|
+
return request.auth != null &&
|
|
893
|
+
"edge-assignment-helper" in ruleHelper &&
|
|
894
|
+
(
|
|
895
|
+
(
|
|
896
|
+
"roles" in user &&
|
|
897
|
+
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.roles &&
|
|
898
|
+
getRolePermission(user.roles[ruleHelper["edge-assignment-helper"].permissionCheckPath].role, ruleHelper["edge-assignment-helper"].permissionCheckPath, 'assign')
|
|
899
|
+
) ||
|
|
900
|
+
(
|
|
901
|
+
"specialPermissions" in user &&
|
|
902
|
+
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.specialPermissions &&
|
|
903
|
+
"assign" in user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath] &&
|
|
904
|
+
user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath]["assign"]
|
|
905
|
+
)
|
|
906
|
+
)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function canAssignSubCreatePath(user, ruleHelper) {
|
|
910
|
+
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
911
|
+
return (
|
|
912
|
+
!("subCreate" in request.resource.data) ||
|
|
913
|
+
(
|
|
914
|
+
"subCreate" in request.resource.data &&
|
|
915
|
+
request.resource.data.subCreate.keys().size() == 0
|
|
916
|
+
)
|
|
917
|
+
)||
|
|
918
|
+
(
|
|
919
|
+
permissionCheckPath == "-" ||
|
|
920
|
+
request.resource.data.subCreate.rootPath.matches("^" + permissionCheckPath + ".*$")
|
|
921
|
+
) &&
|
|
922
|
+
(
|
|
923
|
+
(
|
|
924
|
+
"roles" in user &&
|
|
925
|
+
permissionCheckPath in user.roles &&
|
|
926
|
+
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
927
|
+
) ||
|
|
928
|
+
(
|
|
929
|
+
"specialPermissions" in user &&
|
|
930
|
+
permissionCheckPath in user.specialPermissions &&
|
|
931
|
+
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
932
|
+
user.specialPermissions[permissionCheckPath]["assign"]
|
|
933
|
+
)
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function canList() {
|
|
939
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
940
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
941
|
+
return canAssign(user, ruleHelper);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function canCreate() {
|
|
945
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
946
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
947
|
+
return noPermissionData() && canAssign(user, ruleHelper) && canAssignSubCreatePath(user, ruleHelper);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function noPermissionData() {
|
|
951
|
+
return request.resource.data.roles.size() == 0 && request.resource.data.specialPermissions.size() == 0;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
955
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
956
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
957
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
958
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function canGet () {
|
|
962
|
+
return resource == null ||
|
|
963
|
+
("userId" in resource.data && resource.data.userId == "") ||
|
|
964
|
+
("userId" in resource.data && resource.data.userId == request.auth.uid) ||
|
|
965
|
+
canAssign(get(/databases/$(database)/documents/users/$(request.auth.uid)).data, get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data);
|
|
966
|
+
}
|
|
967
|
+
allow get: if canGet();
|
|
968
|
+
allow list: if canList();
|
|
969
|
+
allow create: if canCreate();
|
|
970
|
+
allow update: if canUpdate();
|
|
971
|
+
allow delete: if false // TODO if isTemplate is true... can delete... otherwise users never deleted just removed from collection paths
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
match /databases/{database}/documents/{seg1} {
|
|
975
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
976
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
977
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
978
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
979
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
980
|
+
}
|
|
981
|
+
function checkPermission(collectionPath, permissionCheck) {
|
|
982
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
983
|
+
let skipPaths = ["collection-data", "users", "staged-users", "events", "rule-helpers"];
|
|
984
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
985
|
+
return !(collectionPath in skipPaths) &&
|
|
986
|
+
request.auth != null &&
|
|
987
|
+
collectionPath in ruleHelper &&
|
|
988
|
+
"permissionCheckPath" in ruleHelper[collectionPath] &&
|
|
989
|
+
(
|
|
990
|
+
ruleHelper[collectionPath].permissionCheckPath == "-" ||
|
|
991
|
+
collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$")
|
|
992
|
+
) &&
|
|
993
|
+
(
|
|
994
|
+
(
|
|
995
|
+
"roles" in user &&
|
|
996
|
+
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
997
|
+
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, ruleHelper[collectionPath].permissionCheckPath, permissionCheck)
|
|
998
|
+
) ||
|
|
999
|
+
(
|
|
1000
|
+
"specialPermissions" in user &&
|
|
1001
|
+
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
1002
|
+
permissionCheck in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
1003
|
+
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath][permissionCheck]
|
|
1004
|
+
)
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
match /{seg2} {
|
|
1008
|
+
allow get: if checkPermission(seg1 + "-" + seg2, "read");
|
|
1009
|
+
allow list: if checkPermission(seg1, "read");
|
|
1010
|
+
allow create: if checkPermission(seg1, "write");
|
|
1011
|
+
allow update: if checkPermission(seg1, "write");
|
|
1012
|
+
allow delete: if checkPermission(seg1, "delete");
|
|
1013
|
+
match /{seg3} {
|
|
1014
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
1015
|
+
allow list: if checkPermission(seg1 + "-" + seg2, "read");
|
|
1016
|
+
allow create: if checkPermission(seg1 + "-" + seg2, "write");
|
|
1017
|
+
allow update: if checkPermission(seg1 + "-" + seg2, "write");
|
|
1018
|
+
allow delete: if checkPermission(seg1 + "-" + seg2, "delete");
|
|
1019
|
+
match /{seg4} {
|
|
1020
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
1021
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
1022
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
1023
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
1024
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "delete");
|
|
1025
|
+
|
|
1026
|
+
match /{seg5} {
|
|
1027
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
1028
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
1029
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
1030
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
1031
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "delete");
|
|
1032
|
+
match /{seg6} {
|
|
1033
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
1034
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
1035
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
1036
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
1037
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "delete");
|
|
1038
|
+
match /{seg7} {
|
|
1039
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6 + "-" + seg7, "read");
|
|
1040
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
1041
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
1042
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
1043
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "delete");
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
609
1054
|
|
|
610
1055
|
|
|
611
1056
|
## License
|