From c5ad4cd04cf4a6b54eaba35d62ccebff0a31acd6 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 26 Oct 2020 12:14:31 +1100 Subject: [PATCH 1/7] Cloud Validator --- _includes/cloudcode/cloud-code.md | 169 +++++++++++++++++++++++++++--- 1 file changed, 152 insertions(+), 17 deletions(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 83129f7e8..0acde9815 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -24,6 +24,8 @@ Parse.Cloud.define("averageStars", async (request) => { sum += results[i].get("stars"); } return sum / results.length; +},{ + fields:['movie'] }); ``` @@ -125,6 +127,137 @@ If there is an error, the response in the client looks like: } ``` +## Implementing cloud function validation + +*Available only on parse-server cloud code starting 4.-.-* + +It's important to make sure the parameters provided to 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. + +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. + +```javascript +Parse.Cloud.define("averageStars", async (request) => { + const query = new Parse.Query("Review"); + query.equalTo("movie", request.params.movie); + const results = await query.find(); + let sum = 0; + for (let i = 0; i < results.length; ++i) { + sum += results[i].get("stars"); + } + return sum / results.length; +},{ + fields : ['movie'], + requireUser: true +}); +``` + +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 trigger, knowing that `request.params.movie` is defined, as well as `request.user`. + +## More Advanced Validation + +*Available only on parse-server cloud code starting 4.-.-* + +Often, not only is it important that `request.params.movie` is defined, but also that it is the correct data type. You can do this by providing an `Object` to the `fields` parameter in the Validator. + +```javascript +Parse.Cloud.define("averageStars", async (request) => { + const query = new Parse.Query("Review"); + query.equalTo("movie", request.params.movie); + const results = await query.find(); + let sum = 0; + for (let i = 0; i < results.length; ++i) { + sum += results[i].get("stars"); + } + return sum / results.length; +},{ + fields : { + movie : { + required: true, + type: String, + options: val => { + return val < 20; + }, + error: "Movie must be less than 20 characters" + } + }, + requireUserKeys: { + accType : { + options: 'reviewer', + error: 'Only reviewers can get average stars' + } + } +}); +``` + +This function will only run if: +- `request.params.movie` is defined +- `request.params.movie` is a String +- `request.params.movie` is less than 20 characters +- `request.user` is defined +- `request.user.get('accType')` is defined +- `request.user.get('accType')` is equal to 'reviewer' + +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. + +```javascript +Parse.Cloud.beforeSave(Parse.User, () => { + // any additional beforeSave logic here +}, { + fields: { + accType: { + default: 'viewer', + constant: true + }, + }, +}); +``` +This means that the field `accType` on `Parse.User` will be 'viewer' on signup, and will be unchangable, unless `masterKey` is provided. + +The full range of Built-In Validation Options are: + +- `requireMaster`, whether the function requires a `masterKey` to run +- `requireUser`, whether the function requires a `request.user` to run +- `validateMasterKey`, whether the validator should run on `masterKey` (defaults to false) +- `fields`, an `Array` or `Object` of fields that are required on the request. +- `requireUserKeys`, an `Array` of fields to be validated on `request.user` + +The full range of Built-In Validation Options on `.fields` are: + +- `type`, the type of the `request.params.field` or `request.object.get(field)` +- `default`, what the field should default to if it's `null`, +- `required`, whether the field is required. +- `options`, a singular option, array of options, or custom function of allowed values for the field. +- `constant`, whether the field is immutable. +- `error`, a custom error message if validation fails. + +You can also pass a function to the Validator. This can help you apply reoccuring logic to your cloud code. + +```javascript +const validationRules = request => { + if (request.master) { + return; + } + if (!request.user || request.user.id !== 'masterUser') { + throw 'Unauthorized'; + } +} + +Parse.Cloud.define('adminFunction', request => { +// do admin code here, confident that request.user.id is masterUser, or masterKey is provided +},validationRules) + +Parse.Cloud.define('adminFunctionTwo', request => { +// do admin code here, confident that request.user.id is masterUser, or masterKey is provided +},validationRules) + +``` + +### Some considerations to be aware of +- 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 in the shortest time. +- 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. + +This range of options should help you write more secure cloud code. If you need help in any way, feel free to reach out at our [developer supported community forum](https://community.parseplatform.org/). + # Cloud Jobs 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,7 +304,7 @@ Viewing jobs is supported on parse-dashboard starting version 1.0.19, but you ca ## beforeSave -### Implementing validation +### Implementing data validation 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. @@ -179,12 +312,16 @@ Let's take a look at our movie review example. When you're choosing how many sta ```javascript Parse.Cloud.beforeSave("Review", (request) => { - if (request.object.get("stars") < 1) { - throw "you cannot give less than one star"; - } - - if (request.object.get("stars") > 5) { - throw "you cannot give more than five stars"; +// do any additional beforeSave logic here +},{ + fields: { + stars : { + required:true, + options: stars => { + return stars >= 1 && stars =< 5; + }, + error: 'Your review must be between one and five stars' + } } }); @@ -216,7 +353,9 @@ If you want to use `beforeSave` for a predefined class in the Parse JavaScript S ```javascript Parse.Cloud.beforeSave(Parse.User, async (request) => { // code here -}) +}, + // Validation Object or Validation Function +) ``` ## afterSave @@ -289,17 +428,13 @@ const afterSave = function afterSave(request) { 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: ```javascript -Parse.Cloud.beforeDelete("Album", (request) => { +Parse.Cloud.beforeDelete("Album", async (request) => { const query = new Parse.Query("Photo"); query.equalTo("album", request.object); - query.count() - .then((count) => { - if (count > 0) { - throw "Can't delete album if it still has photos."; - }) - .catch((error) { - throw "Error " + error.code + " : " + error.message + " when getting photo count."; - }); + const count = await query.count({useMasterKey:true}) + if (count > 0) { + throw "Can't delete album if it still has photos."; + } }); ``` From 371797488a1ad21a3576032f866f00779ceb2172 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 26 Oct 2020 12:28:05 +1100 Subject: [PATCH 2/7] Update cloud-code.md --- _includes/cloudcode/cloud-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 0acde9815..cfe75c22f 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -223,7 +223,7 @@ The full range of Built-In Validation Options are: The full range of Built-In Validation Options on `.fields` are: -- `type`, the type of the `request.params.field` or `request.object.get(field)` +- `type`, the type of the `request.params[field]` or `request.object.get(field)` - `default`, what the field should default to if it's `null`, - `required`, whether the field is required. - `options`, a singular option, array of options, or custom function of allowed values for the field. From eb47eb087213acf488deaf4ec7aaa403a903e0af Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Mon, 26 Oct 2020 18:54:10 +0000 Subject: [PATCH 3/7] Apply nits from code review --- _includes/cloudcode/cloud-code.md | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index cfe75c22f..29391e1a2 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -151,13 +151,13 @@ Parse.Cloud.define("averageStars", async (request) => { }); ``` -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 trigger, knowing that `request.params.movie` is defined, as well as `request.user`. +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`. ## More Advanced Validation *Available only on parse-server cloud code starting 4.-.-* -Often, not only is it important that `request.params.movie` is defined, but also that it is the correct data type. You can do this by providing an `Object` to the `fields` parameter in the Validator. +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. ```javascript Parse.Cloud.define("averageStars", async (request) => { @@ -213,24 +213,24 @@ Parse.Cloud.beforeSave(Parse.User, () => { ``` This means that the field `accType` on `Parse.User` will be 'viewer' on signup, and will be unchangable, unless `masterKey` is provided. -The full range of Built-In Validation Options are: +The full range of built-in Validation Options are: -- `requireMaster`, whether the function requires a `masterKey` to run -- `requireUser`, whether the function requires a `request.user` to run -- `validateMasterKey`, whether the validator should run on `masterKey` (defaults to false) -- `fields`, an `Array` or `Object` of fields that are required on the request. -- `requireUserKeys`, an `Array` of fields to be validated on `request.user` +- `requireMaster`: whether the function requires a `masterKey` to run. +- `requireUser`: whether the function requires a `request.user` to run. +- `validateMasterKey`: whether the validator should run on `masterKey` (defaults to false). +- `fields`: an `Array` or `Object` of fields that are required on the request. +- `requireUserKeys`: an `Array` of fields to be validated on `request.user`. -The full range of Built-In Validation Options on `.fields` are: +The full range of built-in Validation Options on `.fields` are: -- `type`, the type of the `request.params[field]` or `request.object.get(field)` -- `default`, what the field should default to if it's `null`, -- `required`, whether the field is required. -- `options`, a singular option, array of options, or custom function of allowed values for the field. -- `constant`, whether the field is immutable. -- `error`, a custom error message if validation fails. +- `type`: the type of the `request.params[field]` or `request.object.get(field)`. +- `default`: what the field should default to if it's `null`. +- `required`: whether the field is required. +- `options`: a singular option, array of options, or custom function of allowed values for the field. +- `constant`: whether the field is immutable. +- `error`: a custom error message if validation fails. -You can also pass a function to the Validator. This can help you apply reoccuring logic to your cloud code. +You can also pass a function to the Validator. This can help you apply reoccuring logic to your Cloud Code. ```javascript const validationRules = request => { @@ -253,10 +253,10 @@ Parse.Cloud.define('adminFunctionTwo', request => { ``` ### Some considerations to be aware of -- 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 in the shortest time. +- 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. - 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. -This range of options should help you write more secure cloud code. If you need help in any way, feel free to reach out at our [developer supported community forum](https://community.parseplatform.org/). +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/). # Cloud Jobs From 07e0293eb9ec42d13811cf8223b05694ad0d92c5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 27 Oct 2020 10:51:48 +1100 Subject: [PATCH 4/7] Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/cloudcode/cloud-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 29391e1a2..4acbeccc0 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -131,7 +131,7 @@ If there is an error, the response in the client looks like: *Available only on parse-server cloud code starting 4.-.-* -It's important to make sure the parameters provided to 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. +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. 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. From 5a9eb3ac51ffcd980fb33f274d2db5e00b8722d6 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 27 Oct 2020 10:52:07 +1100 Subject: [PATCH 5/7] Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/cloudcode/cloud-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 4acbeccc0..744059307 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -153,7 +153,7 @@ Parse.Cloud.define("averageStars", async (request) => { 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`. -## More Advanced Validation +### More Advanced Validation *Available only on parse-server cloud code starting 4.-.-* From 431d37d6696ca1e3f13fbebf06464ff08d44df75 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 27 Oct 2020 10:52:18 +1100 Subject: [PATCH 6/7] Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/cloudcode/cloud-code.md | 1 - 1 file changed, 1 deletion(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 744059307..97cbf6989 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -155,7 +155,6 @@ If the rules specified in the validator object aren't met, the Cloud Function wo ### More Advanced Validation -*Available only on parse-server cloud code starting 4.-.-* 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. From 67bcc130e74f429535395c89a82a09ee738c9a29 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 28 Oct 2020 05:36:41 +1100 Subject: [PATCH 7/7] Update _includes/cloudcode/cloud-code.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/cloudcode/cloud-code.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/_includes/cloudcode/cloud-code.md b/_includes/cloudcode/cloud-code.md index 97cbf6989..a7c567d2f 100644 --- a/_includes/cloudcode/cloud-code.md +++ b/_includes/cloudcode/cloud-code.md @@ -24,8 +24,6 @@ Parse.Cloud.define("averageStars", async (request) => { sum += results[i].get("stars"); } return sum / results.length; -},{ - fields:['movie'] }); ```