-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfirestore.rules
165 lines (146 loc) · 6.39 KB
/
firestore.rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// Validate write operations
function validateWrite(affectedKeys){
// Keys that the authenticated user should not be able to update
let protectedKeys = [
'admin',
'stripeCustomerId',
'stripeSubscriptionId',
'stripePriceId',
'stripeSubscriptionStatus'
];
return (
// Make sure `email` is a string
futureData().email is string
// Require `email` be between 3 and 500 chars
// 254 is technically the limit but overshoot to be safe (stackoverflow.com/a/574698)
&& futureData().email.trim().size() >= 3
&& futureData().email.trim().size() <= 500
&& (
// Make sure `name` either doesn't exist or ...
!('name' in futureData().keys())
|| (
// Make sure `name` is a string
futureData().name is string
// And require that it's between 1 and 144 chars
&& futureData().name.trim().size() >= 1
&& futureData().name.trim().size() <= 144
)
)
// Make sure no protected keys were affected
&& affectedKeys.hasAny(protectedKeys) == false
// Alternatively, instead of protected keys you could have an allow list
//&& futureData().keys().hasOnly(allowedKeys)
);
}
// The authenticated user can only read their own doc
allow read: if isUser(userId);
// For create/update we call a validation function and pass in affected keys
allow create: if isUser(userId) && validateWrite(futureData().keys());
allow update: if isUser(userId) && validateWrite(affectedKeys());
// The user doc can't be deleted (better to add an `isDeleted` field if that's needed)
allow delete: if false;
}
match /items/{itemId} {
// Validate write operations
function validateWrite(affectedKeys){
return (
// Make sure `name` is a string
futureData().name is string
// Require `name` be between 1 and 144 chars
&& futureData().name.trim().size() >= 1
&& futureData().name.trim().size() <= 144
// Make sure `featured` is a bool if it exists
&& (!('featured' in futureData().keys()) || futureData().featured is bool)
// Write rules specific to the user's plan
// By default this ensures only certain plans can update `featured`
// Uncomment the following line after you've added your Stripe plans below
//&& validateWriteForPlan(affectedKeys)
);
}
function validateWriteForPlan(affectedKeys){
// Add your Stripe plans here (also called "Price IDs")
let starterPlan = 'price_xxxxxxxxxxxxxxx';
let proPlan = 'price_xxxxxxxxxxxxxxx';
let businessPlan = 'price_xxxxxxxxxxxxxxx';
// Specify protected keys for each plan
// Currently we prevent updating of `featured` if user has no plan or is on starter plan
let protectedKeysNoPlan = ['featured'];
let protectedKeysStarter = ['featured'];
// Add protected keys for the other plans if you'd like
//let protectedKeysPro = [];
// Get extra user data
let user = getUserData();
// Specify write conditions for each plan (currently just check each plan's protected keys)
// Note: There must be a `userHasPlan` check for each plan or write will fail for missing plans
return (
(userHasNoPlan(user) && affectedKeys.hasAny(protectedKeysNoPlan) == false)
|| (userHasPlan(user, starterPlan) && affectedKeys.hasAny(protectedKeysStarter) == false)
|| (userHasPlan(user, proPlan))
|| (userHasPlan(user, businessPlan))
// Example: Here's how you'd require the name "divjoy is cool" under the business plan
//|| (userHasPlan(user, businessPlan) && futureData().name == "divjoy is cool")
);
}
// Can only read item if the authenticated user is the owner
allow read: if isOwner();
// This would allow reads from any user
//allow read: if true;
// For create/update we call a validation function and pass in affected keys
allow create: if wouldBeOwner() && validateWrite(futureData().keys());
// Notice when updating we need to make sure authenticated user is currently the owner
// and that they would still be the owner if the write is successful (aka they can't change the owner)
allow update: if isOwner() && wouldBeOwner() && validateWrite(affectedKeys());
// Users can delete their own items
allow delete: if isOwner();
}
// Helper functions that simplify our rules
// Check if authenticated user's `uid` matches the specified `userId`
function isUser(userId) {
return request.auth.uid != null && request.auth.uid == userId;
}
// Get current data
function currentData() {
return resource.data;
}
// Get future data (the final data set if update goes through)
function futureData() {
return request.resource.data;
}
// Check if authenticated user's `uid` matches data `owner`
function isOwner(){
return isUser(currentData().owner);
}
// Check if authenticated user's `uid` matches future data `owner`
function wouldBeOwner(){
return isUser(futureData().owner);
}
// Get keys affected by an update
// Requires a diff between `futureData` and `currentData`
function affectedKeys() {
return futureData().diff(currentData()).affectedKeys();
}
// Query for extra user data belonging to the authenticated user
function getUserData(){
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
}
// Check if user has an active plan
function planIsActive(user){
return 'stripeSubscriptionStatus' in user
&& [user.stripeSubscriptionStatus].toSet().hasAny(['active', 'trialing']);
}
// Check if user has the specified plan
function userHasPlan(user, plan){
return planIsActive(user)
&& 'stripePriceId' in user
&& user.stripePriceId == plan
}
// Check if user has no plan
function userHasNoPlan(user){
return planIsActive(user) == false;
}
}
}