@edgedev/firebase 1.7.8 → 1.7.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 +129 -7
- package/package.json +2 -2
- package/src/firestore.rules +285 -0
- package/pnpm-lock.yaml +0 -2317
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ A Vue 3 / Nuxt 3 Plugin or Nuxt 3 global composable for firebase authentication
|
|
|
12
12
|
**[Await and response](#responses)**
|
|
13
13
|
**[Firestore Rules](#firestore-rules)**
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Before diving into the documentation, it's important to note that when using this package, you should always use `await` or wait for promises to resolve. This ensures that the Rule Helpers work correctly and provides the necessary information for verifying user access rights. Failing to wait for promises may lead to inconsistencies in access control and unexpected behavior in your application. For more information about how this class handles user permissions, please refer to the section below: **Rule Helpers: Managing User Permissions in Firestore**.
|
|
18
|
+
|
|
15
19
|
# Installation
|
|
16
20
|
|
|
17
21
|
pnpm install @edgedev/firebase
|
|
@@ -105,7 +109,13 @@ const edgeFirebase = inject("edgeFirebase");
|
|
|
105
109
|
</script>
|
|
106
110
|
```
|
|
107
111
|
|
|
108
|
-
### Firebase Trigger
|
|
112
|
+
### Firebase Trigger functions.
|
|
113
|
+
|
|
114
|
+
These function react to updates in the `staged-users` Firestore collection. This trigger is designed to help maintain data consistency between the `staged-users` and `users` collections. When a document in the `staged-users` collection is updated, the trigger performs checks and updates the corresponding user data in the `users` collection, ensuring that both collections stay in sync.
|
|
115
|
+
|
|
116
|
+
The trigger considers various scenarios such as the presence of a `userId` field, differences between the old and new `templateUserId` fields, and event processing status. It uses helper functions like `setUser`, `shouldProcess`, and `markProcessed` to manage these scenarios and make the necessary updates to the `users` collection. These functions handle tasks like updating or creating user documents, checking if an event should be processed, and marking an event as processed.
|
|
117
|
+
|
|
118
|
+
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.
|
|
109
119
|
|
|
110
120
|
User mangement requires setting up in this firestore trigger function and helper functions in your firebase functions:
|
|
111
121
|
|
|
@@ -253,7 +263,7 @@ edgeFirebase.addUser({
|
|
|
253
263
|
subCreate: {
|
|
254
264
|
rootPath: 'myItems',
|
|
255
265
|
role: 'admin',
|
|
256
|
-
|
|
266
|
+
dynamicDocumentFieldValue: 'name',
|
|
257
267
|
documentStructure: {
|
|
258
268
|
name: '',
|
|
259
269
|
},
|
|
@@ -299,14 +309,33 @@ After someoene has been added as a user they will need to "self register" to beg
|
|
|
299
309
|
|
|
300
310
|
```typescript
|
|
301
311
|
interface userRegister {
|
|
302
|
-
email
|
|
303
|
-
password
|
|
312
|
+
email?: string;
|
|
313
|
+
password?: string;
|
|
304
314
|
meta: object;
|
|
305
315
|
registrationCode: string;
|
|
306
316
|
dynamicDocumentFieldValue?: string;
|
|
307
317
|
}
|
|
308
318
|
```
|
|
309
319
|
|
|
320
|
+
#### Registration using Microsoft Provider.
|
|
321
|
+
|
|
322
|
+
Calling this will generate a Microsoft Sign In Popup and register the user using the Microsoft credentials.
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
edgeFirebase.registerUser(
|
|
326
|
+
{
|
|
327
|
+
meta: {
|
|
328
|
+
firstName: "John",
|
|
329
|
+
lastName: "Doe"
|
|
330
|
+
} // This is just an example of meta, it can contain any fields and any number of fields.
|
|
331
|
+
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.
|
|
332
|
+
dynamicDocumentFieldValue: "" // Optional - See explaintion above about self registration and dynamic collectionPath for user roles.
|
|
333
|
+
},
|
|
334
|
+
'microsoft', // This is the authProvider only 'email' or 'microsoft' are supported, default is 'email',
|
|
335
|
+
["mail.read", "calendars.read"] // This is a list of scopes to pass to Microsoft, the field is optional.
|
|
336
|
+
);
|
|
337
|
+
```
|
|
338
|
+
|
|
310
339
|
|
|
311
340
|
|
|
312
341
|
### Explanation of permissions
|
|
@@ -408,7 +437,13 @@ Remove user special permissions:
|
|
|
408
437
|
);
|
|
409
438
|
```
|
|
410
439
|
|
|
440
|
+
### Rule Helpers: Managing User Permissions in Firestore
|
|
411
441
|
|
|
442
|
+
The package provides a utility designed to assist in managing user permissions for various actions in your Firestore project. By taking a `collectionPath` and an `action` as input parameters, it determines the user's role and special permissions and saves a `RuleCheck` object to the `rule-helpers` collection.
|
|
443
|
+
|
|
444
|
+
The `RuleCheck` object contains the permission type, permission check path, full path, and action, providing the necessary information to verify the user's access rights. By iterating through the user's roles and special permissions, the class identifies the correct permission check path and type.
|
|
445
|
+
|
|
446
|
+
The class plays a crucial role in maintaining data security and access control within your Firestore project. It ensures that users can only perform actions they are authorized to, based on their roles and special permissions.
|
|
412
447
|
|
|
413
448
|
### Remove user
|
|
414
449
|
|
|
@@ -420,6 +455,26 @@ edgeFirebase.removeUser(docId);
|
|
|
420
455
|
|
|
421
456
|
|
|
422
457
|
|
|
458
|
+
### Delete Self
|
|
459
|
+
|
|
460
|
+
This function allows a user to delete their own account. It removes the user's document from both the `users` and `staged-users` collections in the database and also deletes the user's authentication record. The function returns an `actionResponse` object indicating the success or failure of the operation.
|
|
461
|
+
|
|
462
|
+
#### Usage
|
|
463
|
+
|
|
464
|
+
To delete the current user's account, call the `deleteSelf` function:
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
const response = await edgeFirebase.deleteSelf();
|
|
468
|
+
|
|
469
|
+
if (response.success) {
|
|
470
|
+
console.log("Account deleted successfully.");
|
|
471
|
+
} else {
|
|
472
|
+
console.log("Failed to delete account:", response.message);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
|
|
423
478
|
### Users Snapshot Data
|
|
424
479
|
|
|
425
480
|
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.
|
|
@@ -478,7 +533,7 @@ interface permissions {
|
|
|
478
533
|
|
|
479
534
|
# Firebase Authentication
|
|
480
535
|
|
|
481
|
-
|
|
536
|
+
### Email and Password Login:
|
|
482
537
|
|
|
483
538
|
```javascript
|
|
484
539
|
edgeFirebase.logIn(
|
|
@@ -489,7 +544,33 @@ interface permissions {
|
|
|
489
544
|
);
|
|
490
545
|
```
|
|
491
546
|
|
|
492
|
-
|
|
547
|
+
### Log In with Microsoft
|
|
548
|
+
|
|
549
|
+
This function allows users to log in using their Microsoft account. You can also specify an array of provider scopes if you want to request additional permissions from the user. The function returns a Promise that resolves when the sign-in process is complete. If the user does not exist, it will trigger an error and log the user out.
|
|
550
|
+
|
|
551
|
+
#### Usage
|
|
552
|
+
|
|
553
|
+
To log in with a Microsoft account, call the `logInWithMicrosoft` function. You can also pass an array of provider scopes as an optional parameter.
|
|
554
|
+
|
|
555
|
+
```javascript
|
|
556
|
+
// Log in using Microsoft account without additional provider scopes
|
|
557
|
+
edgeFirebase.logInWithMicrosoft();
|
|
558
|
+
|
|
559
|
+
// Log in using Microsoft account with additional provider scopes
|
|
560
|
+
const providerScopes = ["User.Read", "Calendars.Read"];
|
|
561
|
+
edgeFirebase.logInWithMicrosoft(providerScopes);
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### Parameters
|
|
565
|
+
|
|
566
|
+
- `providerScopes` (optional): An array of strings representing the additional provider scopes to request from the user. Defaults to an empty array.
|
|
567
|
+
|
|
568
|
+
#### Returns
|
|
569
|
+
|
|
570
|
+
A Promise that resolves when the sign-in process is complete. The Promise resolves to void, but any errors that occur during the sign-in process are captured and stored in the `this.user.logInError` and `this.user.logInErrorMessage` properties.
|
|
571
|
+
|
|
572
|
+
#### After Login, User information is contained in: edgeFirebase.user
|
|
573
|
+
|
|
493
574
|
The user object is reactive and contains these items:
|
|
494
575
|
```typescript
|
|
495
576
|
interface UserDataObject {
|
|
@@ -607,6 +688,20 @@ edgeFirebase.storeDoc("myItems", addItem);
|
|
|
607
688
|
```
|
|
608
689
|
Note: When a document is written to the collection several other keys are added that can be referenced: **doc_created_at**(timestamp of doc creation), **last_updated**(timestamp document last written), **uid**(the user id of the user that updated or created the document).
|
|
609
690
|
|
|
691
|
+
### Updating a Document Field(s)
|
|
692
|
+
|
|
693
|
+
In contrast to the `storeDoc` method which adds or updates an entire document, you can use `edgeFirebase.changeDoc(collectionPath, docId, object)` to update individual fields in a document. This method allows you to specify the collection path, document ID, and the fields to update in the form of an object. It will only update the fields provided in the object while keeping the existing data in the document intact.
|
|
694
|
+
|
|
695
|
+
```javascript
|
|
696
|
+
<script setup>
|
|
697
|
+
const docId = "exampleDocumentId";
|
|
698
|
+
const updateItem = { title: "Updated Cool Thing" };
|
|
699
|
+
edgeFirebase.changeDoc("myItems", docId, updateItem);
|
|
700
|
+
</script>
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
In this example, the `changeDoc` method will update the title field of the specified document with the new value while preserving other fields. This is particularly useful when you need to modify a single field or a subset of fields in a document without affecting the rest of the data.
|
|
704
|
+
|
|
610
705
|
### Getting a single Document.
|
|
611
706
|
If you want to query a single document from a collection use: **edgeFirebase.getDocData(collectionPath, docId)**
|
|
612
707
|
```javascript
|
|
@@ -626,7 +721,7 @@ const singleDoc = edgeFirebase.removeDoc("myItems", docId);
|
|
|
626
721
|
```
|
|
627
722
|
|
|
628
723
|
# Firestore Snapshot Listeners
|
|
629
|
-
### Starting a
|
|
724
|
+
### Starting a Snapshot listener on a collection
|
|
630
725
|
To start a snapshot listen on a collection use: **edgeFirebase.startSnapshot(collectionPath)**
|
|
631
726
|
```javascript
|
|
632
727
|
<script setup>
|
|
@@ -643,7 +738,22 @@ Once you have started a snapshot reactive data for that snapshot will be availab
|
|
|
643
738
|
</div>
|
|
644
739
|
</template>
|
|
645
740
|
```
|
|
741
|
+
### Starting a Snapshot Listener on a Document
|
|
742
|
+
|
|
743
|
+
To start a snapshot listener on a specific document within a collection, use the `edgeFirebase.startDocumentSnapshot(collectionPath, docId)` method.
|
|
744
|
+
|
|
745
|
+
```javascript
|
|
746
|
+
<script setup>
|
|
747
|
+
edgeFirebase.startDocumentSnapshot("myItems", "exampleDocId");
|
|
748
|
+
</script>
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
Once you have started a snapshot listener on a document, reactive data for that snapshot will be available with `edgeFirebase.data[collectionPath + '/' + docId]`. This method first checks if the user has read permission for the specified document. If the user has permission, it starts the snapshot listener and updates the reactive data object accordingly. If the user doesn't have permission, it returns an error message indicating the lack of read access.
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
|
|
646
755
|
### Snapshot listeners can also be queried, sorted, and limited.
|
|
756
|
+
|
|
647
757
|
#### Query and Sort are an array of objects, Limit is a number
|
|
648
758
|
(if passing more than one query on different keys, FireStore may make you create indexes)
|
|
649
759
|
```typescript
|
|
@@ -675,7 +785,19 @@ edgeFirebase.stopSnapshot("myItems");
|
|
|
675
785
|
</setup>
|
|
676
786
|
```
|
|
677
787
|
|
|
788
|
+
When stopping a snapshot listener on a specific document within a collection, use the combined `collectionPath + '/' + docId` as the parameter for the `edgeFirebase.stopSnapshot()` method.
|
|
789
|
+
|
|
790
|
+
For example:
|
|
791
|
+
|
|
792
|
+
```javascript
|
|
793
|
+
<script setup>
|
|
794
|
+
const documentPath = "myItems/exampleDocId";
|
|
795
|
+
edgeFirebase.stopSnapshot(documentPath);
|
|
796
|
+
</script>
|
|
797
|
+
```
|
|
798
|
+
|
|
678
799
|
# Firestore Static Collection Data
|
|
800
|
+
|
|
679
801
|
To get static data from a collection use the Object: **edgeFirebase.SearchStaticData()**. Static search is done from a class to handle pagination better.
|
|
680
802
|
```javascript
|
|
681
803
|
const staticSearch = new edgeFirebase.SearchStaticData();
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgedev/firebase",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.9",
|
|
4
4
|
"description": "Vue 3 / Nuxt 3 Plugin or Nuxt 3 plugin for firebase authentication and firestore.",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
7
|
+
"postinstall": "if [ ! -f ./firestore.rules ]; then echo 'rules_version = \\'2\\';' > ./firestore.rules; else sed -i '1s/^/rules_version = \\'2\\';\\n/' ./firestore.rules; fi && awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./src/firestore.rules | sed 's/\\/\\\\/g; s/&/\\&/g' | sed -e 's/\"/\\\\\"/g' -e 's/\//\\\\\//g' | sed -e '1s/^/\\/\\/ #EDGE FIREBASE RULES START\\n/' -e '$s/$/\\n\\/\\/ #EDGE FIREBASE RULES END/' >> ./firestore.rules"
|
|
8
8
|
},
|
|
9
9
|
"author": "Seth Fischer",
|
|
10
10
|
"keywords": [
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
rules_version = '2';
|
|
2
|
+
service cloud.firestore {
|
|
3
|
+
|
|
4
|
+
match /databases/{database}/documents/events/{event} {
|
|
5
|
+
allow read: if false;
|
|
6
|
+
allow create: if false;
|
|
7
|
+
allow update: if false;
|
|
8
|
+
allow delete: if false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
match /databases/{database}/documents/rule-helpers/{helper} {
|
|
12
|
+
allow read: if false;
|
|
13
|
+
allow create: if request.auth.uid == request.resource.data.uid;
|
|
14
|
+
allow update: if request.auth.uid == request.resource.data.uid;
|
|
15
|
+
allow delete: if false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
match /databases/{database}/documents/users/{user} {
|
|
19
|
+
function readSelf() {
|
|
20
|
+
return resource == null ||
|
|
21
|
+
(
|
|
22
|
+
"userId" in resource.data &&
|
|
23
|
+
resource.data.userId == request.auth.uid
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
allow read: if readSelf();
|
|
28
|
+
allow create: if false;
|
|
29
|
+
allow update: if false;
|
|
30
|
+
allow delete: if false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
match /databases/{database}/documents/collection-data/{collectionPath} {
|
|
34
|
+
// TODO: these rules need tested.
|
|
35
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
36
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
37
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
38
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
39
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
40
|
+
}
|
|
41
|
+
function canAssign() {
|
|
42
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
43
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data['edge-assignment-helper'];
|
|
44
|
+
return collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$") &&
|
|
45
|
+
(
|
|
46
|
+
"specialPermissions" in user &&
|
|
47
|
+
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
48
|
+
"assign" in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
49
|
+
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath]["assign"]
|
|
50
|
+
) ||
|
|
51
|
+
(
|
|
52
|
+
"roles" in user &&
|
|
53
|
+
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
54
|
+
"role" in user.roles[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
55
|
+
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, collectionPath, "assign")
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
allow read: if request.auth != null; // All signed in users can read collection-data
|
|
59
|
+
allow create: if canAssign();
|
|
60
|
+
allow update: if canAssign();
|
|
61
|
+
allow delete: if canAssign();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
match /databases/{database}/documents/staged-users/{user} {
|
|
65
|
+
|
|
66
|
+
function canUpdate() {
|
|
67
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
68
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
resource == null ||
|
|
72
|
+
request.resource.data.userId == resource.data.userId ||
|
|
73
|
+
(
|
|
74
|
+
resource.data.userId == "" &&
|
|
75
|
+
(
|
|
76
|
+
request.resource.data.userId == request.auth.uid ||
|
|
77
|
+
request.resource.data.templateUserId == request.auth.uid
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
) &&
|
|
81
|
+
"edge-assignment-helper" in ruleHelper &&
|
|
82
|
+
permissionUpdatesCheck(user, ruleHelper, "roles") &&
|
|
83
|
+
permissionUpdatesCheck(user, ruleHelper, "specialPermssions") &&
|
|
84
|
+
request.auth.uid == request.resource.data.uid;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
function permissionUpdatesCheck(user, ruleHelper, permissionType) {
|
|
89
|
+
return !(permissionType in request.resource.data) ||
|
|
90
|
+
(
|
|
91
|
+
resource.data.userId == request.auth.uid &&
|
|
92
|
+
request.resource.data[permissionType].keys().hasOnly(resource.data[permissionType].keys())
|
|
93
|
+
) ||
|
|
94
|
+
(
|
|
95
|
+
resource.data.userId != request.auth.uid &&
|
|
96
|
+
permissionCheck(permissionType, user, ruleHelper)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
function permissionCheck(permissionType, user, ruleHelper) {
|
|
100
|
+
let lastPathUpdated = ruleHelper["edge-assignment-helper"].fullPath;
|
|
101
|
+
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
102
|
+
return request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 0 ||
|
|
103
|
+
(
|
|
104
|
+
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys().size() == 1 &&
|
|
105
|
+
request.resource.data[permissionType].diff(resource.data[permissionType]).affectedKeys() == [lastPathUpdated].toSet() &&
|
|
106
|
+
(
|
|
107
|
+
permissionCheckPath == "-" ||
|
|
108
|
+
lastPathUpdated.matches("^" + permissionCheckPath + ".*$")
|
|
109
|
+
) &&
|
|
110
|
+
(
|
|
111
|
+
(
|
|
112
|
+
"roles" in user &&
|
|
113
|
+
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
114
|
+
) ||
|
|
115
|
+
(
|
|
116
|
+
"specialPermissions" in user &&
|
|
117
|
+
permissionCheckPath in user.specialPermissions &&
|
|
118
|
+
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
119
|
+
user.specialPermissions[permissionCheckPath]["assign"]
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function canAssign(user, ruleHelper) {
|
|
126
|
+
return request.auth != null &&
|
|
127
|
+
"edge-assignment-helper" in ruleHelper &&
|
|
128
|
+
(
|
|
129
|
+
(
|
|
130
|
+
"roles" in user &&
|
|
131
|
+
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.roles &&
|
|
132
|
+
getRolePermission(user.roles[ruleHelper["edge-assignment-helper"].permissionCheckPath].role, ruleHelper["edge-assignment-helper"].permissionCheckPath, 'assign')
|
|
133
|
+
) ||
|
|
134
|
+
(
|
|
135
|
+
"specialPermissions" in user &&
|
|
136
|
+
ruleHelper["edge-assignment-helper"].permissionCheckPath in user.specialPermissions &&
|
|
137
|
+
"assign" in user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath] &&
|
|
138
|
+
user.specialPermissions[ruleHelper["edge-assignment-helper"].permissionCheckPath]["assign"]
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function canAssignSubCreatePath(user, ruleHelper) {
|
|
144
|
+
let permissionCheckPath = ruleHelper["edge-assignment-helper"].permissionCheckPath;
|
|
145
|
+
return (
|
|
146
|
+
!("subCreate" in request.resource.data) ||
|
|
147
|
+
(
|
|
148
|
+
"subCreate" in request.resource.data &&
|
|
149
|
+
request.resource.data.subCreate.keys().size() == 0
|
|
150
|
+
)
|
|
151
|
+
)||
|
|
152
|
+
(
|
|
153
|
+
permissionCheckPath == "-" ||
|
|
154
|
+
request.resource.data.subCreate.rootPath.matches("^" + permissionCheckPath + ".*$")
|
|
155
|
+
) &&
|
|
156
|
+
(
|
|
157
|
+
(
|
|
158
|
+
"roles" in user &&
|
|
159
|
+
permissionCheckPath in user.roles &&
|
|
160
|
+
getRolePermission(user.roles[permissionCheckPath].role, permissionCheckPath, "assign")
|
|
161
|
+
) ||
|
|
162
|
+
(
|
|
163
|
+
"specialPermissions" in user &&
|
|
164
|
+
permissionCheckPath in user.specialPermissions &&
|
|
165
|
+
"assign" in user.specialPermissions[permissionCheckPath] &&
|
|
166
|
+
user.specialPermissions[permissionCheckPath]["assign"]
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function canList() {
|
|
173
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
174
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
175
|
+
return canAssign(user, ruleHelper);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function canCreate() {
|
|
179
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
180
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
181
|
+
return noPermissionData() && canAssign(user, ruleHelper) && canAssignSubCreatePath(user, ruleHelper);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function noPermissionData() {
|
|
185
|
+
return request.resource.data.roles.size() == 0 && request.resource.data.specialPermissions.size() == 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
189
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
190
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
191
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
192
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function canGet () {
|
|
196
|
+
return resource == null ||
|
|
197
|
+
("userId" in resource.data && resource.data.userId == "") ||
|
|
198
|
+
("userId" in resource.data && resource.data.userId == request.auth.uid) ||
|
|
199
|
+
canAssign(get(/databases/$(database)/documents/users/$(request.auth.uid)).data, get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data);
|
|
200
|
+
}
|
|
201
|
+
allow get: if canGet();
|
|
202
|
+
allow list: if canList();
|
|
203
|
+
allow create: if canCreate();
|
|
204
|
+
allow update: if canUpdate();
|
|
205
|
+
allow delete: if false // TODO if isTemplate is true... can delete... otherwise users never deleted just removed from collection paths
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
match /databases/{database}/documents/{seg1} {
|
|
209
|
+
function getRolePermission(role, collection, permissionCheck) {
|
|
210
|
+
let pathCollectionPermissions = get(/databases/$(database)/documents/collection-data/$(collection)).data;
|
|
211
|
+
let defaultPermissions = get(/databases/$(database)/documents/collection-data/-default-).data;
|
|
212
|
+
return (role in pathCollectionPermissions && pathCollectionPermissions[role][permissionCheck]) ||
|
|
213
|
+
(role in defaultPermissions && defaultPermissions[role][permissionCheck]);
|
|
214
|
+
}
|
|
215
|
+
function checkPermission(collectionPath, permissionCheck) {
|
|
216
|
+
let user = get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
|
|
217
|
+
let skipPaths = ["collection-data", "users", "staged-users", "events", "rule-helpers"];
|
|
218
|
+
let ruleHelper = get(/databases/$(database)/documents/rule-helpers/$(request.auth.uid)).data;
|
|
219
|
+
return !(collectionPath in skipPaths) &&
|
|
220
|
+
request.auth != null &&
|
|
221
|
+
collectionPath in ruleHelper &&
|
|
222
|
+
"permissionCheckPath" in ruleHelper[collectionPath] &&
|
|
223
|
+
(
|
|
224
|
+
ruleHelper[collectionPath].permissionCheckPath == "-" ||
|
|
225
|
+
collectionPath.matches("^" + ruleHelper[collectionPath].permissionCheckPath + ".*$")
|
|
226
|
+
) &&
|
|
227
|
+
(
|
|
228
|
+
(
|
|
229
|
+
"roles" in user &&
|
|
230
|
+
ruleHelper[collectionPath].permissionCheckPath in user.roles &&
|
|
231
|
+
getRolePermission(user.roles[ruleHelper[collectionPath].permissionCheckPath].role, ruleHelper[collectionPath].permissionCheckPath, permissionCheck)
|
|
232
|
+
) ||
|
|
233
|
+
(
|
|
234
|
+
"specialPermissions" in user &&
|
|
235
|
+
ruleHelper[collectionPath].permissionCheckPath in user.specialPermissions &&
|
|
236
|
+
permissionCheck in user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath] &&
|
|
237
|
+
user.specialPermissions[ruleHelper[collectionPath].permissionCheckPath][permissionCheck]
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
match /{seg2} {
|
|
242
|
+
allow get: if checkPermission(seg1 + "-" + seg2, "read");
|
|
243
|
+
allow list: if checkPermission(seg1, "read");
|
|
244
|
+
allow create: if checkPermission(seg1, "write");
|
|
245
|
+
allow update: if checkPermission(seg1 + "-" + seg2, "write");
|
|
246
|
+
allow delete: if checkPermission(seg1, "delete");
|
|
247
|
+
match /{seg3} {
|
|
248
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
249
|
+
allow list: if checkPermission(seg1 + "-" + seg2, "read");
|
|
250
|
+
allow create: if checkPermission(seg1 + "-" + seg2, "write");
|
|
251
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
252
|
+
allow delete: if checkPermission(seg1 + "-" + seg2, "delete");
|
|
253
|
+
match /{seg4} {
|
|
254
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
255
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "read");
|
|
256
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "write");
|
|
257
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
258
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3, "delete");
|
|
259
|
+
|
|
260
|
+
match /{seg5} {
|
|
261
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
262
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "read");
|
|
263
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "write");
|
|
264
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
265
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4, "delete");
|
|
266
|
+
match /{seg6} {
|
|
267
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
268
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "read");
|
|
269
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "write");
|
|
270
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
271
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5, "delete");
|
|
272
|
+
match /{seg7} {
|
|
273
|
+
allow get: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6 + "-" + seg7, "read");
|
|
274
|
+
allow list: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "read");
|
|
275
|
+
allow create: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "write");
|
|
276
|
+
allow update: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6 + "-" + seg7, "write");
|
|
277
|
+
allow delete: if checkPermission(seg1 + "-" + seg2 + "-" + seg3 + "-" + seg4 + "-" + seg5 + "-" + seg6, "delete");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|