Skip to content

Commit 4275a4c

Browse files
dblythyTomWFox
andauthored
Cloud Validator Documentation (#782)
* Cloud Validator * Update cloud-code.md * Apply nits from code review * Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <[email protected]> * Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <[email protected]> * Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <[email protected]> * Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <[email protected]> Co-authored-by: Tom Fox <[email protected]>
1 parent bd64ec8 commit 4275a4c

File tree

1 file changed

+149
-17
lines changed

1 file changed

+149
-17
lines changed

_includes/cloudcode/cloud-code.md

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,136 @@ If there is an error, the response in the client looks like:
125125
}
126126
```
127127

128+
## Implementing cloud function validation
129+
130+
*Available only on parse-server cloud code starting 4.-.-*
131+
132+
It's important to make sure the parameters required for a Cloud function are provided, and are in the necessary format. Starting with Parse Server 4.-.-, you can now specify a validator function or object which will be called prior to your cloud function.
133+
134+
Let's take a look at the `averageStars` example. If you wanted to make sure that `request.params.movie` is provided, and `averageStars` can only be called by logged in users, you could add a validator object to the function.
135+
136+
```javascript
137+
Parse.Cloud.define("averageStars", async (request) => {
138+
const query = new Parse.Query("Review");
139+
query.equalTo("movie", request.params.movie);
140+
const results = await query.find();
141+
let sum = 0;
142+
for (let i = 0; i < results.length; ++i) {
143+
sum += results[i].get("stars");
144+
}
145+
return sum / results.length;
146+
},{
147+
fields : ['movie'],
148+
requireUser: true
149+
});
150+
```
151+
152+
If the rules specified in the validator object aren't met, the Cloud Function won't run. This means that you can confidently build your function, knowing that `request.params.movie` is defined, as well as `request.user`.
153+
154+
### More Advanced Validation
155+
156+
157+
Often, not only is it important that `request.params.movie` is defined, but also that it's the correct data type. You can do this by providing an `Object` to the `fields` parameter in the Validator.
158+
159+
```javascript
160+
Parse.Cloud.define("averageStars", async (request) => {
161+
const query = new Parse.Query("Review");
162+
query.equalTo("movie", request.params.movie);
163+
const results = await query.find();
164+
let sum = 0;
165+
for (let i = 0; i < results.length; ++i) {
166+
sum += results[i].get("stars");
167+
}
168+
return sum / results.length;
169+
},{
170+
fields : {
171+
movie : {
172+
required: true,
173+
type: String,
174+
options: val => {
175+
return val < 20;
176+
},
177+
error: "Movie must be less than 20 characters"
178+
}
179+
},
180+
requireUserKeys: {
181+
accType : {
182+
options: 'reviewer',
183+
error: 'Only reviewers can get average stars'
184+
}
185+
}
186+
});
187+
```
188+
189+
This function will only run if:
190+
- `request.params.movie` is defined
191+
- `request.params.movie` is a String
192+
- `request.params.movie` is less than 20 characters
193+
- `request.user` is defined
194+
- `request.user.get('accType')` is defined
195+
- `request.user.get('accType')` is equal to 'reviewer'
196+
197+
However, the requested user could set 'accType' to reviewer, and then recall the function. Here, you could provide validation on a `Parse.User` `beforeSave` trigger. `beforeSave` validators have a few additional options available, to help you make sure your data is secure.
198+
199+
```javascript
200+
Parse.Cloud.beforeSave(Parse.User, () => {
201+
// any additional beforeSave logic here
202+
}, {
203+
fields: {
204+
accType: {
205+
default: 'viewer',
206+
constant: true
207+
},
208+
},
209+
});
210+
```
211+
This means that the field `accType` on `Parse.User` will be 'viewer' on signup, and will be unchangable, unless `masterKey` is provided.
212+
213+
The full range of built-in Validation Options are:
214+
215+
- `requireMaster`: whether the function requires a `masterKey` to run.
216+
- `requireUser`: whether the function requires a `request.user` to run.
217+
- `validateMasterKey`: whether the validator should run on `masterKey` (defaults to false).
218+
- `fields`: an `Array` or `Object` of fields that are required on the request.
219+
- `requireUserKeys`: an `Array` of fields to be validated on `request.user`.
220+
221+
The full range of built-in Validation Options on `.fields` are:
222+
223+
- `type`: the type of the `request.params[field]` or `request.object.get(field)`.
224+
- `default`: what the field should default to if it's `null`.
225+
- `required`: whether the field is required.
226+
- `options`: a singular option, array of options, or custom function of allowed values for the field.
227+
- `constant`: whether the field is immutable.
228+
- `error`: a custom error message if validation fails.
229+
230+
You can also pass a function to the Validator. This can help you apply reoccuring logic to your Cloud Code.
231+
232+
```javascript
233+
const validationRules = request => {
234+
if (request.master) {
235+
return;
236+
}
237+
if (!request.user || request.user.id !== 'masterUser') {
238+
throw 'Unauthorized';
239+
}
240+
}
241+
242+
Parse.Cloud.define('adminFunction', request => {
243+
// do admin code here, confident that request.user.id is masterUser, or masterKey is provided
244+
},validationRules)
245+
246+
Parse.Cloud.define('adminFunctionTwo', request => {
247+
// do admin code here, confident that request.user.id is masterUser, or masterKey is provided
248+
},validationRules)
249+
250+
```
251+
252+
### Some considerations to be aware of
253+
- The validation function will run prior to your Cloud Code Functions. You can use async and promises here, but try to keep the validation as simple and fast as possible so your cloud requests resolve quickly.
254+
- As previously mentioned, cloud validator objects will not validate if a masterKey is provided, unless `validateMasterKey:true` is set. However, if you set your validator to a function, the function will **always** run.
255+
256+
This range of options should help you write more secure Cloud Code. If you need help in any way, feel free to reach out on our [developer supported community forum](https://community.parseplatform.org/).
257+
128258
# Cloud Jobs
129259

130260
Sometimes you want to execute long running functions, and you don't want to wait for the response. Cloud Jobs are meant for just that.
@@ -171,20 +301,24 @@ Viewing jobs is supported on parse-dashboard starting version 1.0.19, but you ca
171301

172302
## beforeSave
173303

174-
### Implementing validation
304+
### Implementing data validation
175305

176306
Another reason to run code in the cloud is to enforce a particular data format. For example, you might have both an Android and an iOS app, and you want to validate data for each of those. Rather than writing code once for each client environment, you can write it just once with Cloud Code.
177307

178308
Let's take a look at our movie review example. When you're choosing how many stars to give something, you can typically only give 1, 2, 3, 4, or 5 stars. You can't give -6 stars or 1337 stars in a review. If we want to reject reviews that are out of bounds, we can do this with the `beforeSave` method:
179309

180310
```javascript
181311
Parse.Cloud.beforeSave("Review", (request) => {
182-
if (request.object.get("stars") < 1) {
183-
throw "you cannot give less than one star";
184-
}
185-
186-
if (request.object.get("stars") > 5) {
187-
throw "you cannot give more than five stars";
312+
// do any additional beforeSave logic here
313+
},{
314+
fields: {
315+
stars : {
316+
required:true,
317+
options: stars => {
318+
return stars >= 1 && stars =< 5;
319+
},
320+
error: 'Your review must be between one and five stars'
321+
}
188322
}
189323
});
190324

@@ -216,7 +350,9 @@ If you want to use `beforeSave` for a predefined class in the Parse JavaScript S
216350
```javascript
217351
Parse.Cloud.beforeSave(Parse.User, async (request) => {
218352
// code here
219-
})
353+
},
354+
// Validation Object or Validation Function
355+
)
220356
```
221357

222358
## afterSave
@@ -289,17 +425,13 @@ const afterSave = function afterSave(request) {
289425
You can run custom Cloud Code before an object is deleted. You can do this with the `beforeDelete` method. For instance, this can be used to implement a restricted delete policy that is more sophisticated than what can be expressed through [ACLs]({{ site.apis.js }}/classes/Parse.ACL.html). For example, suppose you have a photo album app, where many photos are associated with each album, and you want to prevent the user from deleting an album if it still has a photo in it. You can do that by writing a function like this:
290426

291427
```javascript
292-
Parse.Cloud.beforeDelete("Album", (request) => {
428+
Parse.Cloud.beforeDelete("Album", async (request) => {
293429
const query = new Parse.Query("Photo");
294430
query.equalTo("album", request.object);
295-
query.count()
296-
.then((count) => {
297-
if (count > 0) {
298-
throw "Can't delete album if it still has photos.";
299-
})
300-
.catch((error) {
301-
throw "Error " + error.code + " : " + error.message + " when getting photo count.";
302-
});
431+
const count = await query.count({useMasterKey:true})
432+
if (count > 0) {
433+
throw "Can't delete album if it still has photos.";
434+
}
303435
});
304436
```
305437

0 commit comments

Comments
 (0)