-
Notifications
You must be signed in to change notification settings - Fork 0
/
database.rules.json
264 lines (246 loc) · 9.68 KB
/
database.rules.json
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
{
"rules": {
// disallow both read and write by default
// nested rules can not change rules above
".read": false,
".write": false,
"users": {
// read, nobody can (the global user list)
// write, only create new user if not logged in
".read": false,
".write": "(auth !== null) && !data.exists()",
// Currently commented out to allow deletes.
// Make sure the the authenticated user id exists after a write
// ".validate": "newData.hasChild(auth.uid)",
"$userID": {
// read: only auth user can read themselves
// write: only authed user can write a single new user, his uid, that's all
// write: only user-owner can edit himself
// write: can not delete (newData value can't be null)
// note: first the "user" is authenticated against Firebase,
// then we independently create a user model in our Firebase DB
// the authentication and our USER concept are two different things
".read": "$userID === auth.uid",
// You can write yourself only. But not delete.
//".write": "($userID === auth.uid) && (newData.val() != null)",
".write": "$userID === auth.uid",
".validate": "newData.hasChildren(['created'])",
"created": {
// Ensure type::timestamp and that you can not update it
".validate": "data.exists() && data.val() === newData.val() || newData.val() === now"
},
"settings": {
".validate": "!newData.exists() || newData.isString() && root.child('userSettings').child(newData.val()).child('user').val() === $userID"
},
"channels": {
// trick to limit to one radio
".validate": "!data.exists()",
"$channelID": {
// we would like to prevent a user to had a channel he does no own
// but we don't store on the channel@model the user who owns it
// (because, historically, firebase stores the userId as provider:provider-id,
// so it is extremely easy to find the user on social networks)
// so instead we just prevent a user to add to himself a radio that already
// has tracks in -> so no radio hijack
".validate": "root.child('channels').child($channelID).exists() && !root.child('users').child(auth.uid).child('channels').child($channelID).exists() && !root.child('channels').child($channelID).child('tracks').exists()"
}
}/*,
"$other": {
".validate": false
}*/
}
},
"userSettings": {
".read": false,
".write": false,
"$userSettingsID": {
// read: only auth user can read its settings
// write: only user-owner can edit his settings
// write: only logged-in-user without a user settings can create a new one to himself
".read": "auth != null && (data.child('user').val() === auth.uid)",
".write": "auth != null && ((data.child('user').val() === auth.uid) || (root.child('users').child(auth.uid).hasChild('settings') === false))",
".validate": "newData.hasChild('user')",
"user": {
".validate": "newData.val() === auth.uid"
},
"isRemoteActive": {
".validate": "newData.isBoolean()"
},
"signedUserAgreement": {
".validate": "newData.isBoolean() && newData.val() === true"
},
"playedChannels": {
"$channel": {
".validate": "newData.isBoolean() && root.child('channels').child($channel).exists()"
}
},
"$other": {
".validate": false
}
}
},
"channels": {
".read": true,
".write": false,
".indexOn": ["created", "slug", "isFeatured", "title"],
"$channelID": {
".read": true,
// write: only the user owner can write a channel
// write: only a user with no channel can write a new one to himself
".write": "auth != null && (root.child('users').child(auth.uid).child('channels').child($channelID).exists() || (!data.exists() && !root.child('users').child(auth.uid).child('channels').exists()))",
".validate": "newData.hasChildren(['slug', 'title', 'created']) || !newData.exists()",
"channelPublic": {
".validate": "newData.isString() && root.child('channelPublics').child(newData.val()).child('channel').val() == $channelID"
},
"created": {
// Ensure type::number and that you can not update it
// OR allow a timestamp within the last second
".validate": "data.exists() && data.val() === newData.val() || (newData.val() >= now -1000 && newData.val() <= now)"
},
"updated": {
// Ensure type::number and that you can not update it
".validate": "newData.val() == now"
},
"isFeatured": {
".validate": "newData.isBoolean() && (data.exists() && newData.val() === data.val() || newData.val() === false)"
},
"isPremium": {
".validate": "newData.isBoolean() && (data.exists() && newData.val() === data.val() || newData.val() === false)"
},
"link": {
".validate": "newData.isString() && newData.val().length < 150"
},
// todo: create a separate table to maintain unique slugs otherwise slug can be hijacked
"slug": {
".validate": "newData.isString() && newData.val().length > 2 && newData.val().length < 100"
},
"title": {
".validate": "newData.isString() && newData.val().length > 2 && newData.val().length < 32"
},
"body": {
".validate": "newData.isString() && newData.val().length < 300"
},
"tracks": {
"$track": {
".validate": "root.child('tracks').hasChild($track) && root.child('tracks').child($track).child('channel').val() === $channelID"
}
},
"favoriteChannels": {
"$favoriteChannel": {
".validate": "root.child('channels').child($favoriteChannel).exists()"
}
},
"image": {
".validate": "newData.isString()"
},
"images": {
"$image": {
".validate": "root.child('images').hasChild($image) && root.child('images').child($image).child('channel').val() === $channelID"
}
},
"coordinatesLatitude": {
".validate": "newData.isNumber() && newData.val() >= -90 && newData.val() <= 90"
},
"coordinatesLongitude": {
".validate": "newData.isNumber() && newData.val() >= -180 && newData.val() <= 180"
},
"$other": {
".validate": false
}
}
},
"channelPublics": {
// read: no one can read the full list of the channelPublics
// write: no one can write the list
".read": false,
".write": false,
".indexOn": ["followers"],
"$channelPublic": {
".read": true,
// write: user is logged in && has one channel
".write": "auth !== null && root.child('users').child(auth.uid).child('channels').exists()",
".validate": "newData.hasChild('channel')",
// there is no existing data
// && user has the channel newData in is channels
// && this channel has no publicChannel
"channel": {
".validate": "data.exists() && data.val() === newData.val() || !data.exists() && root.child('users').child(auth.uid).child('channels').child(newData.val()).exists() && !root.child('channels').child(newData.val()).child('channelPublic').exists()"
},
"followers": {
// validate: user can only add a radio he owns
// validate: owner can't add himself
"$follower": {
".validate": "root.child('users').child(auth.uid).child('channels').hasChild($follower) && root.child('channels').child($follower).child('channelPublic').val() !== $channelPublic"
}
},
"$other": {
".validate": false
}
}
},
"images": {
".read": true,
".write": false,
".indexOn": ["channel"],
"$imageID": {
// ".write": "auth !== null && root.child('users').child(auth.uid).child('channels').child(newData.child('channel').val()).exists()",
".write": "auth !== null && newData.exists() && root.child('users/' + auth.uid + '/channels').child(newData.child('channel').val()).exists() || auth !== null && !newData.exists() && root.child('users/' + auth.uid + '/channels').child(data.child('channel').val()).exists()",
".validate": "newData.hasChildren(['channel', 'src'])",
"channel": {
".validate": "root.child('users').child(auth.uid).child('channels').child(newData.val()).exists()"
},
"src": {
".validate": "newData.isString()"
},
"created": {
// Ensure type::timestamp and that you can not update it
".validate": "data.exists() && data.val() === newData.val() || newData.val() === now"
},
"$other": {
".validate": false
}
}
},
"tracks": {
".read": true,
".write": false,
".indexOn": ["channel", "title"],
"$trackID": {
".write": "auth !== null && newData.exists() && root.child('users/' + auth.uid + '/channels').child(newData.child('channel').val()).exists() || auth !== null && !newData.exists() && root.child('users/' + auth.uid + '/channels').child(data.child('channel').val()).exists()",
".validate": "newData.hasChildren(['channel', 'url', 'ytid', 'title', 'created'])",
"created": {
// Ensure type::timestamp and that you can not update it
".validate": "data.exists() && data.val() === newData.val() || newData.val() === now"
},
"url": {
".validate": "newData.isString() && newData.val().length > 3"
},
"discogsUrl": {
".validate": "newData.isString() && newData.val().length > 3 && newData.val().length < 300 || newData.val().length === 0"
},
"title": {
".validate": "newData.isString() && newData.val().length > 0"
},
"body": {
".validate": "newData.isString()"
},
"ytid": {
".validate": "newData.isString()"
},
"channel": {
// can only be updated if the value matches the authenticated users channel id
".validate": "newData.isString() && root.child('users').child(auth.uid).child('channels').child(newData.val()).exists()"
},
"mediaNotAvailable": {
".validate": "newData.isBoolean()"
},
"$other": {
".validate": false
}
}
},
"$others": {
".validate": false
}
}
}