From 8cb0260869934235ab89ee1c07a4e96fb4d3f3de Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 5 Jan 2021 00:11:14 +1100 Subject: [PATCH 01/27] update server guide --- _includes/parse-server/adapters.md | 7 + _includes/parse-server/backers.md | 46 +++ _includes/parse-server/bleeding-edge.md | 9 + _includes/parse-server/development.md | 5 +- _includes/parse-server/experimental.md | 50 ++++ _includes/parse-server/graphql.md | 325 ++++++++++++++++++++++ _includes/parse-server/logging.md | 23 ++ _includes/parse-server/upgrading-to-v3.md | 245 ++++++++++++++++ _includes/parse-server/usage.md | 201 +++++++++++-- parse-server.md | 7 + 10 files changed, 898 insertions(+), 20 deletions(-) create mode 100644 _includes/parse-server/adapters.md create mode 100644 _includes/parse-server/backers.md create mode 100644 _includes/parse-server/bleeding-edge.md create mode 100644 _includes/parse-server/experimental.md create mode 100644 _includes/parse-server/graphql.md create mode 100644 _includes/parse-server/logging.md create mode 100644 _includes/parse-server/upgrading-to-v3.md diff --git a/_includes/parse-server/adapters.md b/_includes/parse-server/adapters.md new file mode 100644 index 000000000..e1723ceea --- /dev/null +++ b/_includes/parse-server/adapters.md @@ -0,0 +1,7 @@ +# Adapters + +All official adapters are distributed as scoped pacakges on [npm (@parse)](https://www.npmjs.com/search?q=scope%3Aparse). + +Some well maintained adapters are also available on the [Parse Server Modules](https://github.com/parse-server-modules) organization. + +You can also find more adapters maintained by the community by searching on [npm](https://www.npmjs.com/search?q=parse-server%20adapter&page=1&ranking=optimal). \ No newline at end of file diff --git a/_includes/parse-server/backers.md b/_includes/parse-server/backers.md new file mode 100644 index 000000000..6eb2f25de --- /dev/null +++ b/_includes/parse-server/backers.md @@ -0,0 +1,46 @@ +# Contributors + +This project exists thanks to all the people who contribute... we'd love to see your face on this list! + + + +# Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Backers + +Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer) + + \ No newline at end of file diff --git a/_includes/parse-server/bleeding-edge.md b/_includes/parse-server/bleeding-edge.md new file mode 100644 index 000000000..10229fb6b --- /dev/null +++ b/_includes/parse-server/bleeding-edge.md @@ -0,0 +1,9 @@ +# Want to ride the bleeding edge? + +It is recommend to use builds deployed npm for many reasons, but if you want to use +the latest not-yet-released version of parse-server, you can do so by depending +directly on this branch: + +``` +npm install parse-community/parse-server.git#master +``` \ No newline at end of file diff --git a/_includes/parse-server/development.md b/_includes/parse-server/development.md index 85e7bf6db..8f9f1f043 100644 --- a/_includes/parse-server/development.md +++ b/_includes/parse-server/development.md @@ -1,5 +1,7 @@ # Development Guide +We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server notes](https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md). + ## Running Parse Server for development Normally, when you run a standalone Parse Server, the [latest release that has been pushed to npm](https://www.npmjs.com/package/parse-server) will be used. This is great if you are interested in just running Parse Server, but if you are developing a new feature or fixing a bug you will want to use the latest code on your development environment. @@ -59,6 +61,3 @@ The following is a breakdown of the various files you will find in the Parse Ser * [triggers.js](https://github.com/parse-community/parse-server/wiki/triggers.js) - cloud code methods for handling database trigger events * [users.js](https://github.com/parse-community/parse-server/wiki/users.js) - handle the /users and /login routes -## Contributing - -We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server notes](https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md). diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md new file mode 100644 index 000000000..aee252676 --- /dev/null +++ b/_includes/parse-server/experimental.md @@ -0,0 +1,50 @@ +# Experimental + +Experimental Features are items that you are either experimenting with that will eventually become full features of your application, or items that will be removed from the system if they are proven to not work well. + +These features may not be approprate for production, so use at your own risk. + +## Direct Access + +* `directAccess`: Replaces HTTP Interface when using JS SDK in current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. + +## Idempotency + +This feature deduplicates identical requests that are received by Parse Server mutliple times, typically due to network issues or network adapter access restrictions on mobile operating systems. + +Identical requests are identified by their request header `X-Parse-Request-Id`. Therefore a client request has to include this header for deduplication to be applied. Requests that do not contain this header cannot be deduplicated and are processed normally by Parse Server. This means rolling out this feature to clients is seamless as Parse Server still processes request without this header when this feature is enbabled. + +This feature needs to be enabled on the client side to send the header and on the server to process the header. Refer to the specific Parse SDK docs to see whether the feature is supported yet. + +Deduplication is only done for object creation and update (`POST` and `PUT` requests). Deduplication is not done for object finding and deletion (`GET` and `DELETE` requests), as these operations are already idempotent by definition. + +Configutation: +```js +let api = new ParseServer({ + idempotencyOptions: { + paths: [".*"], // enforce for all requests + ttl: 120 // keep request IDs for 120s + } +} +``` +Parameters: + +* `idempotencyOptions` (`Object`): Setting this enables idempotency enforcement for the specified paths. +* `idempotencyOptions.paths`(`Array`): An array of path patterns that have to match the request path for request deduplication to be enabled. + * The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specify the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. + + Examples: + + * `.*`: all paths, includes the examples below + * `functions/.*`: all functions + * `jobs/.*`: all jobs + * `classes/.*`: all classes + * `functions/.*`: all functions + * `users`: user creation / update + * `installations`: installation creation / update + +* `idempotencyOptions.ttl`: The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. + +#### Notes + +- This feature is currently only available for MongoDB and not for Postgres. \ No newline at end of file diff --git a/_includes/parse-server/graphql.md b/_includes/parse-server/graphql.md new file mode 100644 index 000000000..ab70cc8ae --- /dev/null +++ b/_includes/parse-server/graphql.md @@ -0,0 +1,325 @@ +# GraphQL + +[GraphQL](https://graphql.org/), developed by Facebook, is an open-source data query and manipulation language for APIs. In addition to the traditional REST API, Parse Server automatically generates a GraphQL API based on your current application schema. Parse Server also allows you to define your custom GraphQL queries and mutations, whose resolvers can be bound to your cloud code functions. + +## Running + +### Using the CLI + +The easiest way to run the Parse GraphQL API is through the CLI: + +```bash +$ npm install -g parse-server mongodb-runner +$ mongodb-runner start +$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground +``` + +After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. + +Note: + +- Do **NOT** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps. + +### Using Docker + +You can also run the Parse GraphQL API inside a Docker container: + +```bash +$ git clone https://github.com/parse-community/parse-server +$ cd parse-server +$ docker build --tag parse-server . +$ docker run --name my-mongo -d mongo +``` + +#### Running the Parse Server Image + +```bash +$ docker run --name my-parse-server --link my-mongo:mongo -v config-vol:/parse-server/config -p 1337:1337 -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground +``` + +Note: + +* If you want to use [Cloud Code]({{ site.baseUrl }}/cloudcode/guide/) feature, please add `-v cloud-code-vol:/parse-server/cloud --cloud /parse-server/cloud/main.js` to command above. Make sure the `main.js` file is available in the `cloud-code-vol` directory before run this command. Otherwise, an error will occur. + +After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. + +### Using Express.js + +You can also mount the GraphQL API in an Express.js application together with the REST API or solo. You first need to create a new project and install the required dependencies: + +```bash +$ mkdir my-app +$ cd my-app +$ npm install parse-server express --save +``` + +Then, create an `index.js` file with the following content: + +```js +const express = require('express'); +const { default: ParseServer, ParseGraphQLServer } = require('parse-server'); + +const app = express(); + +const parseServer = new ParseServer({ + databaseURI: 'mongodb://localhost:27017/test', + appId: 'APPLICATION_ID', + masterKey: 'MASTER_KEY', + serverURL: 'http://localhost:1337/parse', + publicServerURL: 'http://localhost:1337/parse' +}); + +const parseGraphQLServer = new ParseGraphQLServer( + parseServer, + { + graphQLPath: '/graphql', + playgroundPath: '/playground' + } +); + +app.use('/parse', parseServer.app); // (Optional) Mounts the REST API +parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API +parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production + +app.listen(1337, function() { + console.log('REST API running on http://localhost:1337/parse'); + console.log('GraphQL API running on http://localhost:1337/graphql'); + console.log('GraphQL Playground running on http://localhost:1337/playground'); +}); +``` + +And finally start your app: + +```bash +$ npx mongodb-runner start +$ node index.js +``` + +After starting the app, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. + +Note: + +- Do **NOT** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps. + +## Checking the API health + +Run the following: + +```graphql +query Health { + health +} +``` + +You should receive the following response: + +```json +{ + "data": { + "health": true + } +} +``` + +## Creating your first class + +Since your application does not have any schema yet, you can use the `createClass` mutation to create your first class. Run the following: + +```graphql +mutation CreateClass { + createClass( + name: "GameScore" + schemaFields: { + addStrings: [{ name: "playerName" }] + addNumbers: [{ name: "score" }] + addBooleans: [{ name: "cheatMode" }] + } + ) { + name + schemaFields { + name + __typename + } + } +} +``` + +You should receive the following response: + +```json +{ + "data": { + "createClass": { + "name": "GameScore", + "schemaFields": [ + { + "name": "objectId", + "__typename": "SchemaStringField" + }, + { + "name": "updatedAt", + "__typename": "SchemaDateField" + }, + { + "name": "createdAt", + "__typename": "SchemaDateField" + }, + { + "name": "playerName", + "__typename": "SchemaStringField" + }, + { + "name": "score", + "__typename": "SchemaNumberField" + }, + { + "name": "cheatMode", + "__typename": "SchemaBooleanField" + }, + { + "name": "ACL", + "__typename": "SchemaACLField" + } + ] + } + } +} +``` + +## Using automatically generated operations + +Parse Server learned from the first class that you created and now you have the `GameScore` class in your schema. You can now start using the automatically generated operations! + +Run the following to create your first object: + +```graphql +mutation CreateGameScore { + createGameScore( + fields: { + playerName: "Sean Plott" + score: 1337 + cheatMode: false + } + ) { + id + updatedAt + createdAt + playerName + score + cheatMode + ACL + } +} +``` + +You should receive a response similar to this: + +```json +{ + "data": { + "createGameScore": { + "id": "XN75D94OBD", + "updatedAt": "2019-09-17T06:50:26.357Z", + "createdAt": "2019-09-17T06:50:26.357Z", + "playerName": "Sean Plott", + "score": 1337, + "cheatMode": false, + "ACL": null + } + } +} +``` + +You can also run a query to this new class: + +```graphql +query GameScores { + gameScores { + results { + id + updatedAt + createdAt + playerName + score + cheatMode + ACL + } + } +} +``` + +You should receive a response similar to this: + +```json +{ + "data": { + "gameScores": { + "results": [ + { + "id": "XN75D94OBD", + "updatedAt": "2019-09-17T06:50:26.357Z", + "createdAt": "2019-09-17T06:50:26.357Z", + "playerName": "Sean Plott", + "score": 1337, + "cheatMode": false, + "ACL": null + } + ] + } + } +} +``` + +## Customizing your GraphQL Schema + +Parse GraphQL Server allows you to create a custom GraphQL schema with own queries and mutations to be merged with the auto-generated ones. You can resolve these operations using your regular cloud code functions. + +To start creating your custom schema, you need to code a `schema.graphql` file and initialize Parse Server with `--graphQLSchema` and `--cloud` options: + +```bash +$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --publicServerURL http://localhost:1337/parse --cloud ./cloud/main.js --graphQLSchema ./cloud/schema.graphql --mountGraphQL --mountPlayground +``` + +### Creating your first custom query + +Use the code below for your `schema.graphql` and `main.js` files. Then restart your Parse Server. + +```graphql +# schema.graphql +extend type Query { + hello: String! @resolve +} +``` + +```js +// main.js +Parse.Cloud.define('hello', async () => { + return 'Hello world!'; +}); +``` + +You can now run your custom query using GraphQL Playground: + +```graphql +query { + hello +} +``` + +You should receive the response below: + +```json +{ + "data": { + "hello": "Hello world!" + } +} +``` + +## Learning more + +The [Parse GraphQL Guide]({{ site.baseUrl }}/graphql/guide/) is a very good source for learning how to use the Parse GraphQL API. + +You also have a very powerful tool inside your GraphQL Playground. Please look at the right side of your GraphQL Playground. You will see the `DOCS` and `SCHEMA` menus. They are automatically generated by analyzing your application schema. Please refer to them and learn more about everything that you can do with your Parse GraphQL API. + +Additionally, the [GraphQL Learn Section](https://graphql.org/learn/) is a very good source to learn more about the power of the GraphQL language. \ No newline at end of file diff --git a/_includes/parse-server/logging.md b/_includes/parse-server/logging.md new file mode 100644 index 000000000..180f33f96 --- /dev/null +++ b/_includes/parse-server/logging.md @@ -0,0 +1,23 @@ +# Logging + +Parse Server will, by default, log: +* to the console +* daily rotating files as new line delimited JSON + +Logs are also viewable in Parse Dashboard. + +**Want to log each request and response?** + +Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` + +**Want logs to be in placed in a different folder?** + +Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` + +**Want to log specific levels?** + +Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL` + +**Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)?** + +Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` \ No newline at end of file diff --git a/_includes/parse-server/upgrading-to-v3.md b/_includes/parse-server/upgrading-to-v3.md new file mode 100644 index 000000000..d3e8d84c3 --- /dev/null +++ b/_includes/parse-server/upgrading-to-v3.md @@ -0,0 +1,245 @@ +# Upgrading Parse Server to version 3.0.0 + +Starting 3.0.0, parse-server uses the JS SDK version 2.0. +In short, parse SDK v2.0 removes the backbone style callbacks as well as the Parse.Promise object in favor of native promises. +All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution. + +With the 3.0.0, parse-server has completely revamped it's cloud code interface. Gone are the backbone style calls, welcome promises and async/await. + +In order to leverage those new nodejs features, you'll need to run at least node 8.10. We've dropped the support of node 6 a few months ago, so if you haven't made the change yet, now would be a really good time to take the plunge. + +## Migrating Cloud Code Hooks + +### Synchronous validations: + +```js +// before +Parse.Cloud.beforeSave('MyClassName', function(request, response) { + if (!passesValidation(request.object)) { + response.error('Ooops something went wrong'); + } else { + response.success(); + } +}); + +// after +Parse.Cloud.beforeSave('MyClassName', (request) => { + if (!passesValidation(request.object)) { + throw new Parse.Error('Ooops something went wrong'); + } +}); +``` + +```js +// before +Parse.Cloud.define('doFunction', function(request, response) { + if (!passesValidation(request.user)) { + response.error('Ooops something went wrong'); + } else { + response.success('Do function ran successfully'); + } +}); + +// after +Parse.Cloud.define('doFunction', (request) => { + if (!passesValidation(request.user)) { + throw new Parse.Error('Ooops something went wrong'); + } + return 'Do function ran successfully'; +}); +``` + +All methods are wrapped in promises, so you can freely `throw` `Error`, `Parse.Error` or `string` to mark an error. + +### Asynchronous validations: + +For asynchronous code, you can use promises or async / await. + +Consider the following beforeSave call that would replace the contents of a fileURL with a proper copy of the image as a Parse.File. + +```js +// before +Parse.Cloud.beforeSave('Post', function(request, response) { + Parse.Cloud.httpRequest({ + url: request.object.get('fileURL'), + success: function(contents) { + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + file.save({ + success: function() { + request.object.set('file', file); + response.success(); + }, + error: response.error + }); + }, + error: response.error + }); +}); +``` + +As we can see the current way, with backbone style callbacks is quite tough to read and maintain. +It's also not really trivial to handle errors, as you need to pass the response.error to each error handlers. + +Now it can be modernized with promises: + +```js +// after (with promises) +Parse.Cloud.beforeSave('MyClassName', (request) => { + return Parse.Cloud.httpRequest({ + url: request.object.get('fileURL') + }).then((contents) => { + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + request.object.set('file', file); + return file.save(); + }); +}); +``` + +And you can even make it better with async/await. + +```js +// after with async/await +Parse.Cloud.beforeSave('MyClassName', async (request) => { + const contents = await Parse.Cloud.httpRequest({ + url: request.object.get('fileURL') + }); + + const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); + request.object.set('file', file); + await file.save(); +}); +``` + +## Aborting hooks and functions + +In order to abort a `beforeSave` or any hook, you now need to throw an error: + +```js +// after with async/await +Parse.Cloud.beforeSave('MyClassName', async (request) => { + // valid, will result in a Parse.Error(SCRIPT_FAILED, 'Something went wrong') + throw 'Something went wrong' + // also valid, will fail with Parse.Error(SCRIPT_FAILED, 'Something else went wrong') + throw new Error('Something else went wrong') + // using a Parse.Error is also valid + throw new Parse.Error(1001, 'My error') +}); +``` + +## Migrating Functions + +Cloud Functions can be migrated the same way as cloud code hooks. +In functions, the response object is not passed anymore, as it is in cloud code hooks + +Continuing with the image downloader example: + +```js +// before +Parse.Cloud.define('downloadImage', function(request, response) { + Parse.Cloud.httpRequest({ + url: request.params.url, + success: function(contents) { + const file = new Parse.File(request.params.name, { base64: contents.buffer.toString('base64') }); + file.save({ + success: function() { + response.success(file); + }, + error: response.error + }); + }, + error: response.error + }); +}); +``` + +You would call this method with: + +```js +Parse.Cloud.run('downloadImage',{ + url: 'https://example.com/file', + name: 'my-file' +}); +``` + +To migrate this function you would follow the same practices as the ones before, and we'll jump right away to the async/await implementation: + +```js +// after with async/await +Parse.Cloud.define('downloadImage', async (request) => { + const { + url, name + } = request.params; + const response = await Parse.Cloud.httpRequest({ url }); + + const file = new Parse.File(name, { base64: response.buffer.toString('base64') }); + await file.save(); + return file; +}); +``` + +## Migrating jobs + +As with hooks and functions, jobs don't have a status object anymore. +The message method has been moved to the request object. + +```js +// before +Parse.Cloud.job('downloadImageJob', function(request, status) { + var query = new Parse.Query('ImagesToDownload'); + query.find({ + success: function(images) { + var done = 0; + for (var i = 0; i { + request.message('Doing ' + image.get('url')); + const contents = await Parse.Cloud.httpRequest({ + url: image.get('url') + }); + request.message('Got ' + image.get('url')); + const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') }); + await file.save(); + request.message('Saved ' + image.get('url')); + }); + await Promise.all(promises); +}); + +``` + +As you can see the new implementation is more concise, easier to read and maintain. + diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index ecb0e5bea..0a105bdc9 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -5,41 +5,36 @@ Parse Server is meant to be mounted on an [Express](http://expressjs.com/) app. The constructor returns an API object that conforms to an [Express Middleware](http://expressjs.com/en/api.html#app.use). This object provides the REST endpoints for a Parse app. Create an instance like so: ```js -var api = new ParseServer({ +const api = new ParseServer({ databaseURI: 'mongodb://your.mongo.uri', cloud: './cloud/main.js', appId: 'myAppId', - fileKey: 'myFileKey', masterKey: 'mySecretMasterKey', - push: { ... }, // See the Push wiki page - filesAdapter: ..., + serverURL: 'http://localhost:1337/parse' }); ``` -The parameters are as follows: +## Basic Options +* `appId`: A unique identifier for your app. * `databaseURI`: Connection string URI for your MongoDB. * `cloud`: Path to your app’s Cloud Code. -* `appId`: A unique identifier for your app. -* `fileKey`: A key that specifies a prefix used for file storage. For migrated apps, this is necessary to provide access to files already hosted on Parse. * `masterKey`: A key that overrides all permissions. Keep this secret. -* `clientKey`: The client key for your app. (optional) -* `restAPIKey`: The REST API key for your app. (optional) -* `javascriptKey`: The JavaScript key for your app. (optional) -* `dotNetKey`: The .NET key for your app. (optional) +* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. +* `port`: The default port is 1337, specify this parameter to use a different port. * `push`: An object containing push configuration. See [Push](#push-notifications) -* `filesAdapter`: An object that implements the [FilesAdapter](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js) interface. For example, [the S3 files adapter](#configuring-file-adapters) * `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). -* `maxUploadSize`: Maximum file upload size. Make sure your server does not restrict max request body size (e.g. nginx.conf `client_max_body_size 100m;`) + +For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: ```js -var express = require('express'); -var ParseServer = require('parse-server').ParseServer; +const express = require('express'); +const ParseServer = require('parse-server').ParseServer; -var app = express(); -var api = new ParseServer({ ... }); +const app = express(); +const api = new ParseServer({ ... }); // Serve the Parse API at /parse URL prefix app.use('/parse', api); @@ -51,3 +46,175 @@ app.listen(port, function() { ``` And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. + +## Additional Options + +### Email verification and password reset + +Verifying user email addresses and enabling password reset via email requires an email adapter. + +You can also use email adapters contributed by the community such as: +- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) +- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) +- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) +- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) +- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) +- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) +- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) +- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) +- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) +- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) + +The Parse Server Configuration Options relating to email verifcation are: + +* `verifyUserEmails`: whether the Parse Server should send mail on user signup +* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for +* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid +* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email +* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. +* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. +* `emailAdapter`: The email adapter. + +```js +const api = ParseServer({ + ...otherOptions, + verifyUserEmails: true, + emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) + preventLoginWithUnverifiedEmail: false, // defaults to false + publicServerURL: 'https://example.com/parse', + appName: 'Parse App', + emailAdapter: { + module: '@parse/simple-mailgun-adapter', + options: { + fromAddress: 'parse@example.com', + domain: 'example.com', + apiKey: 'key-mykey', + } + }, +}); +``` +Note: + +* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. + +### Account Lockout + +Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. + +If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. + +After `duration` minutes of no login attempts, the application will allow the user to try login again. + +*`accountLockout`: Object that contains account lockout rules +*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. +*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. + +```js +const api = ParseServer({ + ...otherOptions, + accountLockout: { + duration: 5, + threshold: 3 + } +}); +``` + +### Password Policy + +Password policy is a good way to enforce that users' passwords are secure. + +Two optional settings can be used to enforce strong passwords. Either one or both can be specified. + +If both are specified, both checks must pass to accept the password + +1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce +2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password + +The full range of options for Password Policy are: + +*`passwordPolicy` is an object that contains the following rules: +*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. +*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords +*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. +*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. +*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) +*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid + +```js +const validatePassword = password => { + if (!password) { + return false; + } + if (password.includes('pass')) { + return false; + } + return true; +} +const api = ParseServer({ + ...otherOptions, + passwordPolicy: { + validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit + validatorCallback: (password) => { return validatePassword(password) }, + validationError: 'Password must contain at least 1 digit.', + doNotAllowUsername: true, + maxPasswordAge: 90, + maxPasswordHistory: 5, + resetTokenValidityDuration: 24*60*60, + resetTokenReuseIfValid: true + } +}); +``` + + +### Custom Pages + +It’s possible to change the default pages of the app and redirect the user to another path or domain. + +```js +const api = ParseServer({ + ...otherOptions, + customPages: { + passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", + verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", + parseFrameURL: "http://yourapp.com/parseFrameURL", + linkSendSuccess: "http://yourapp.com/linkSendSuccess", + linkSendFail: "http://yourapp.com/linkSendFail", + invalidLink: "http://yourapp.com/invalidLink", + invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", + choosePassword: "http://yourapp.com/choosePassword" + } +}) +``` + +## Insecure Options + +When deploying to be production, make sure: + +* `allowClientClassCreation` is not set to `true` +* `mountPlayground` is not set to `true` +* `masterKey` is set to a long and complex string +* `readOnlyMasterKey` if set, is set to a long and complex string +* That your have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 +* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) +* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) + +## Using environment variables to configure Parse Server + +You may configure the Parse Server using environment variables: + +```bash +PORT +PARSE_SERVER_APPLICATION_ID +PARSE_SERVER_MASTER_KEY +PARSE_SERVER_DATABASE_URI +PARSE_SERVER_URL +PARSE_SERVER_CLOUD +``` + +The default port is 1337, to use a different port set the PORT environment variable: + +```bash +$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` + +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file diff --git a/parse-server.md b/parse-server.md index c5a123895..2165e98ee 100644 --- a/parse-server.md +++ b/parse-server.md @@ -16,11 +16,18 @@ sections: - "parse-server/push-notifications.md" - "parse-server/push-notifications-clients.md" - "parse-server/class-level-permissions.md" +- "parse-server/adapters.md" - "parse-server/file-adapters.md" - "parse-server/cache-adapters.md" - "parse-server/live-query.md" - "parse-server/third-party-auth.md" +- "parse-server/graphql.md" +- "parse-server/logging.md" - "parse-server/MongoRocks.md" - "parse-server/MongoReadPreference.md" +- "parse-server/upgrading-to-v3.md" +- "parse-server/experimental.md" +- "parse-server/bleeding-edge.md" - "parse-server/development.md" +- "parse-server/backers.md" --- From db64ac4da6341f95d80c82c249b7e8403860450c Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 5 Jan 2021 00:12:24 +1100 Subject: [PATCH 02/27] Update experimental.md --- _includes/parse-server/experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index aee252676..842659994 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -1,6 +1,6 @@ # Experimental -Experimental Features are items that you are either experimenting with that will eventually become full features of your application, or items that will be removed from the system if they are proven to not work well. +Experimental Features are items that we are experimenting with that will eventually become full features, or items that will be removed from the system if they are proven to not work well. These features may not be approprate for production, so use at your own risk. From 1997c9cc196217d9616fd7a9c0d62c84b298ccff Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 5 Jan 2021 17:16:15 +1100 Subject: [PATCH 03/27] Update usage.md --- _includes/parse-server/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index 0a105bdc9..0f2e87d92 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -194,7 +194,7 @@ When deploying to be production, make sure: * `mountPlayground` is not set to `true` * `masterKey` is set to a long and complex string * `readOnlyMasterKey` if set, is set to a long and complex string -* That your have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 +* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 * You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) * You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) @@ -217,4 +217,4 @@ The default port is 1337, to use a different port set the PORT environment varia $ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY ``` -For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). From 1a323242c00d5b88bd7c9e2a5368512de50704f8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 1 Feb 2021 12:33:56 +1100 Subject: [PATCH 04/27] include config and security --- _includes/common/performance.md | 10 +++++----- _includes/common/security.md | 5 ++++- _includes/parse-server/usage.md | 8 +++++++- parse-server.md | 1 + 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/_includes/common/performance.md b/_includes/common/performance.md index 18c35ce6f..a465a2ff0 100644 --- a/_includes/common/performance.md +++ b/_includes/common/performance.md @@ -35,8 +35,8 @@ Take a look at the following query to retrieve GameScore objects: {% if page.language == "js" %} ```javascript -var GameScore = Parse.Object.extend("GameScore"); -var query = new Parse.Query(GameScore); +const GameScore = Parse.Object.extend("GameScore"); +const query = new Parse.Query(GameScore); query.equalTo("score", 50); query.containedIn("playerName", ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]); @@ -181,8 +181,8 @@ For example, let's say you're tracking high scores for a game in a GameScore cla {% if page.language == "js" %} ```javascript -var GameScore = Parse.Object.extend("GameScore"); -var query = new Parse.Query(GameScore); +const GameScore = Parse.Object.extend("GameScore"); +const query = new Parse.Query(GameScore); query.notEqualTo("playerName", "Michael Yabuti"); query.find().then(function(results) { // Retrieved scores successfully @@ -269,7 +269,7 @@ For example if the User class has a column called state which has values “Sign {% if page.language == "js" %} ```javascript -var query = new Parse.Query(Parse.User); +const query = new Parse.Query(Parse.User); query.notEqualTo("state", "Invited"); ``` {% endif %} diff --git a/_includes/common/security.md b/_includes/common/security.md index cd327b8a8..fb5bd7a61 100644 --- a/_includes/common/security.md +++ b/_includes/common/security.md @@ -547,10 +547,13 @@ The master key should be used carefully. setting `useMasterKey` to `true` only i ```js Parse.Cloud.define("like", async request => { - var post = new Parse.Object("Post"); + const post = new Parse.Object("Post"); post.id = request.params.postId; post.increment("likes"); await post.save(null, { useMasterKey: true }) +}, { + fields: ['postId'], + requireUser: true }); ``` diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index 0a105bdc9..dfd260705 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -47,6 +47,12 @@ app.listen(port, function() { And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. +## Configuration + +Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. + +For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). + ## Additional Options ### Email verification and password reset @@ -190,7 +196,7 @@ const api = ParseServer({ When deploying to be production, make sure: -* `allowClientClassCreation` is not set to `true` +* `allowClientClassCreation` is set to `false` * `mountPlayground` is not set to `true` * `masterKey` is set to a long and complex string * `readOnlyMasterKey` if set, is set to a long and complex string diff --git a/parse-server.md b/parse-server.md index 2165e98ee..9b64901f0 100644 --- a/parse-server.md +++ b/parse-server.md @@ -23,6 +23,7 @@ sections: - "parse-server/third-party-auth.md" - "parse-server/graphql.md" - "parse-server/logging.md" +- "common/security.md" - "parse-server/MongoRocks.md" - "parse-server/MongoReadPreference.md" - "parse-server/upgrading-to-v3.md" From 0e4e3c05b601b27f7e924f8a048fad09db9fd628 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 11:57:50 +1100 Subject: [PATCH 05/27] Update _includes/parse-server/adapters.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/adapters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/adapters.md b/_includes/parse-server/adapters.md index e1723ceea..c799b18d5 100644 --- a/_includes/parse-server/adapters.md +++ b/_includes/parse-server/adapters.md @@ -2,6 +2,6 @@ All official adapters are distributed as scoped pacakges on [npm (@parse)](https://www.npmjs.com/search?q=scope%3Aparse). -Some well maintained adapters are also available on the [Parse Server Modules](https://github.com/parse-server-modules) organization. +Some adapters are also available on the [Parse Server Modules](https://github.com/parse-server-modules) organization. -You can also find more adapters maintained by the community by searching on [npm](https://www.npmjs.com/search?q=parse-server%20adapter&page=1&ranking=optimal). \ No newline at end of file +You can also find more adapters maintained by the community by searching on [npm](https://www.npmjs.com/search?q=parse-server%20adapter&page=1&ranking=optimal). From 769c845161887f16a21a15525d35d3f88850f484 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 11:59:05 +1100 Subject: [PATCH 06/27] Update _includes/parse-server/bleeding-edge.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/bleeding-edge.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/bleeding-edge.md b/_includes/parse-server/bleeding-edge.md index 10229fb6b..4a19dfeed 100644 --- a/_includes/parse-server/bleeding-edge.md +++ b/_includes/parse-server/bleeding-edge.md @@ -1,9 +1,9 @@ # Want to ride the bleeding edge? -It is recommend to use builds deployed npm for many reasons, but if you want to use +It is recommend to use builds deployed to npm for many reasons, but if you want to use the latest not-yet-released version of parse-server, you can do so by depending directly on this branch: ``` npm install parse-community/parse-server.git#master -``` \ No newline at end of file +``` From 12873013f540dbd1404f18a6dde81b27c69419f2 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 11:59:33 +1100 Subject: [PATCH 07/27] Update _includes/parse-server/experimental.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/experimental.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 842659994..bc2f392e6 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -31,7 +31,7 @@ Parameters: * `idempotencyOptions` (`Object`): Setting this enables idempotency enforcement for the specified paths. * `idempotencyOptions.paths`(`Array`): An array of path patterns that have to match the request path for request deduplication to be enabled. - * The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specify the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. +* The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specify the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. Examples: @@ -47,4 +47,4 @@ Parameters: #### Notes -- This feature is currently only available for MongoDB and not for Postgres. \ No newline at end of file +- This feature is currently only available for MongoDB and not for Postgres. From dc9d0027e9db9f42f4ccebb8191e6be8b5d0a564 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:01:18 +1100 Subject: [PATCH 08/27] Update _includes/parse-server/experimental.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index bc2f392e6..023bf2a6e 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -6,7 +6,7 @@ These features may not be approprate for production, so use at your own risk. ## Direct Access -* `directAccess`: Replaces HTTP Interface when using JS SDK in current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. +`directAccess` replaces the HTTP Interface when using the JS SDK in the current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. ## Idempotency From 7e3dc71f78f44903276a3da220ae558b1b158b73 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:03:20 +1100 Subject: [PATCH 09/27] Update _includes/parse-server/logging.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/logging.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/logging.md b/_includes/parse-server/logging.md index 180f33f96..86b0b4c33 100644 --- a/_includes/parse-server/logging.md +++ b/_includes/parse-server/logging.md @@ -4,7 +4,7 @@ Parse Server will, by default, log: * to the console * daily rotating files as new line delimited JSON -Logs are also viewable in Parse Dashboard. +Logs are also viewable in the Parse Dashboard. **Want to log each request and response?** @@ -20,4 +20,4 @@ Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-ser **Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)?** -Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` \ No newline at end of file +Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` From 15947a7e49dfc61e4f5802d9b2bed63f6ba328eb Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:03:38 +1100 Subject: [PATCH 10/27] Update _includes/parse-server/logging.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/logging.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_includes/parse-server/logging.md b/_includes/parse-server/logging.md index 86b0b4c33..8df4cf311 100644 --- a/_includes/parse-server/logging.md +++ b/_includes/parse-server/logging.md @@ -8,7 +8,8 @@ Logs are also viewable in the Parse Dashboard. **Want to log each request and response?** -Set the `VERBOSE` environment variable when starting `parse-server`. Usage :- `VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` +Set the `VERBOSE` environment variable when starting `parse-server`. +```VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY``` **Want logs to be in placed in a different folder?** From db0fd81388ebabafed79f55e4cbb8816380059f1 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:21:05 +1100 Subject: [PATCH 11/27] update --- _includes/parse-server/backers.md | 46 -- _includes/parse-server/configuration.md | 210 +++++++++ _includes/parse-server/experimental.md | 15 +- _includes/parse-server/usage.md | 213 +-------- package-lock.json | 579 ++++++++++++++++++++++++ package.json | 6 +- parse-server.md | 2 +- 7 files changed, 807 insertions(+), 264 deletions(-) delete mode 100644 _includes/parse-server/backers.md create mode 100644 _includes/parse-server/configuration.md diff --git a/_includes/parse-server/backers.md b/_includes/parse-server/backers.md deleted file mode 100644 index 6eb2f25de..000000000 --- a/_includes/parse-server/backers.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributors - -This project exists thanks to all the people who contribute... we'd love to see your face on this list! - - - -# Sponsors - -Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Backers - -Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer) - - \ No newline at end of file diff --git a/_includes/parse-server/configuration.md b/_includes/parse-server/configuration.md new file mode 100644 index 000000000..536dc51f1 --- /dev/null +++ b/_includes/parse-server/configuration.md @@ -0,0 +1,210 @@ +## Configuration + +Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. + +For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). + +## Basic Options + +* `appId`: A unique identifier for your app. +* `databaseURI`: Connection string URI for your MongoDB. +* `cloud`: Path to your app’s Cloud Code. +* `masterKey`: A key that overrides all permissions. Keep this secret. +* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. +* `port`: The default port is 1337, specify this parameter to use a different port. +* `push`: An object containing push configuration. See [Push](#push-notifications) +* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). + +For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. + +The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: + +```js +const express = require('express'); +const ParseServer = require('parse-server').ParseServer; + +const app = express(); +const api = new ParseServer({ ... }); + +// Serve the Parse API at /parse URL prefix +app.use('/parse', api); + +var port = 1337; +app.listen(port, function() { + console.log('parse-server-example running on port ' + port + '.'); +}); +``` + +And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. + +## Additional Options + +### Email verification and password reset + +Verifying user email addresses and enabling password reset via email requires an email adapter. + +You can also use email adapters contributed by the community such as: +- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) +- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) +- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) +- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) +- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) +- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) +- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) +- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) +- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) +- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) + +The Parse Server Configuration Options relating to email verifcation are: + +* `verifyUserEmails`: whether the Parse Server should send mail on user signup +* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for +* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid +* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email +* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. +* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. +* `emailAdapter`: The email adapter. + +```js +const api = ParseServer({ + ...otherOptions, + verifyUserEmails: true, + emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) + preventLoginWithUnverifiedEmail: false, // defaults to false + publicServerURL: 'https://example.com/parse', + appName: 'Parse App', + emailAdapter: { + module: '@parse/simple-mailgun-adapter', + options: { + fromAddress: 'parse@example.com', + domain: 'example.com', + apiKey: 'key-mykey', + } + }, +}); +``` +Note: + +* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. + +### Account Lockout + +Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. + +If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. + +After `duration` minutes of no login attempts, the application will allow the user to try login again. + +*`accountLockout`: Object that contains account lockout rules +*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. +*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. + +```js +const api = ParseServer({ + ...otherOptions, + accountLockout: { + duration: 5, + threshold: 3 + } +}); +``` + +### Password Policy + +Password policy is a good way to enforce that users' passwords are secure. + +Two optional settings can be used to enforce strong passwords. Either one or both can be specified. + +If both are specified, both checks must pass to accept the password + +1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce +2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password + +The full range of options for Password Policy are: + +*`passwordPolicy` is an object that contains the following rules: +*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. +*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords +*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. +*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. +*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) +*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid + +```js +const validatePassword = password => { + if (!password) { + return false; + } + if (password.includes('pass')) { + return false; + } + return true; +} +const api = ParseServer({ + ...otherOptions, + passwordPolicy: { + validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit + validatorCallback: (password) => { return validatePassword(password) }, + validationError: 'Password must contain at least 1 digit.', + doNotAllowUsername: true, + maxPasswordAge: 90, + maxPasswordHistory: 5, + resetTokenValidityDuration: 24*60*60, + resetTokenReuseIfValid: true + } +}); +``` + + +### Custom Pages + +It’s possible to change the default pages of the app and redirect the user to another path or domain. + +```js +const api = ParseServer({ + ...otherOptions, + customPages: { + passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", + verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", + parseFrameURL: "http://yourapp.com/parseFrameURL", + linkSendSuccess: "http://yourapp.com/linkSendSuccess", + linkSendFail: "http://yourapp.com/linkSendFail", + invalidLink: "http://yourapp.com/invalidLink", + invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", + choosePassword: "http://yourapp.com/choosePassword" + } +}) +``` + +## Insecure Options + +When deploying to be production, make sure: + +* `allowClientClassCreation` is set to `false` +* `mountPlayground` is not set to `true` +* `masterKey` is set to a long and complex string +* `readOnlyMasterKey` if set, is set to a long and complex string +* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 +* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) +* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) + +## Using environment variables to configure Parse Server + +You may configure the Parse Server using environment variables: + +```bash +PORT +PARSE_SERVER_APPLICATION_ID +PARSE_SERVER_MASTER_KEY +PARSE_SERVER_DATABASE_URI +PARSE_SERVER_URL +PARSE_SERVER_CLOUD +``` + +The default port is 1337, to use a different port set the PORT environment variable: + +```bash +$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` + +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 842659994..1d17ef697 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -8,6 +8,14 @@ These features may not be approprate for production, so use at your own risk. * `directAccess`: Replaces HTTP Interface when using JS SDK in current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. +Configuration: +```js +const api = new ParseServer({ + //...other configuration + directAccess: true +}); +``` + ## Idempotency This feature deduplicates identical requests that are received by Parse Server mutliple times, typically due to network issues or network adapter access restrictions on mobile operating systems. @@ -18,14 +26,15 @@ This feature needs to be enabled on the client side to send the header and on th Deduplication is only done for object creation and update (`POST` and `PUT` requests). Deduplication is not done for object finding and deletion (`GET` and `DELETE` requests), as these operations are already idempotent by definition. -Configutation: +Configuration: ```js -let api = new ParseServer({ +const api = new ParseServer({ + //...other configuration idempotencyOptions: { paths: [".*"], // enforce for all requests ttl: 120 // keep request IDs for 120s } -} +}); ``` Parameters: diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index d60aa2619..da40b75be 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -12,215 +12,4 @@ const api = new ParseServer({ masterKey: 'mySecretMasterKey', serverURL: 'http://localhost:1337/parse' }); -``` - -## Basic Options - -* `appId`: A unique identifier for your app. -* `databaseURI`: Connection string URI for your MongoDB. -* `cloud`: Path to your app’s Cloud Code. -* `masterKey`: A key that overrides all permissions. Keep this secret. -* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. -* `port`: The default port is 1337, specify this parameter to use a different port. -* `push`: An object containing push configuration. See [Push](#push-notifications) -* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). - -For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. - -The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: - -```js -const express = require('express'); -const ParseServer = require('parse-server').ParseServer; - -const app = express(); -const api = new ParseServer({ ... }); - -// Serve the Parse API at /parse URL prefix -app.use('/parse', api); - -var port = 1337; -app.listen(port, function() { - console.log('parse-server-example running on port ' + port + '.'); -}); -``` - -And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. - -## Configuration - -Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. - -For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). - -## Additional Options - -### Email verification and password reset - -Verifying user email addresses and enabling password reset via email requires an email adapter. - -You can also use email adapters contributed by the community such as: -- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) -- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) -- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) -- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) -- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) -- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) -- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) -- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) -- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) -- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) - -The Parse Server Configuration Options relating to email verifcation are: - -* `verifyUserEmails`: whether the Parse Server should send mail on user signup -* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for -* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid -* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email -* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. -* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. -* `emailAdapter`: The email adapter. - -```js -const api = ParseServer({ - ...otherOptions, - verifyUserEmails: true, - emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) - preventLoginWithUnverifiedEmail: false, // defaults to false - publicServerURL: 'https://example.com/parse', - appName: 'Parse App', - emailAdapter: { - module: '@parse/simple-mailgun-adapter', - options: { - fromAddress: 'parse@example.com', - domain: 'example.com', - apiKey: 'key-mykey', - } - }, -}); -``` -Note: - -* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. - -### Account Lockout - -Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. - -If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. - -After `duration` minutes of no login attempts, the application will allow the user to try login again. - -*`accountLockout`: Object that contains account lockout rules -*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. -*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. - -```js -const api = ParseServer({ - ...otherOptions, - accountLockout: { - duration: 5, - threshold: 3 - } -}); -``` - -### Password Policy - -Password policy is a good way to enforce that users' passwords are secure. - -Two optional settings can be used to enforce strong passwords. Either one or both can be specified. - -If both are specified, both checks must pass to accept the password - -1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce -2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password - -The full range of options for Password Policy are: - -*`passwordPolicy` is an object that contains the following rules: -*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. -*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords -*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. -*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. -*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) -*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid - -```js -const validatePassword = password => { - if (!password) { - return false; - } - if (password.includes('pass')) { - return false; - } - return true; -} -const api = ParseServer({ - ...otherOptions, - passwordPolicy: { - validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit - validatorCallback: (password) => { return validatePassword(password) }, - validationError: 'Password must contain at least 1 digit.', - doNotAllowUsername: true, - maxPasswordAge: 90, - maxPasswordHistory: 5, - resetTokenValidityDuration: 24*60*60, - resetTokenReuseIfValid: true - } -}); -``` - - -### Custom Pages - -It’s possible to change the default pages of the app and redirect the user to another path or domain. - -```js -const api = ParseServer({ - ...otherOptions, - customPages: { - passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", - verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", - parseFrameURL: "http://yourapp.com/parseFrameURL", - linkSendSuccess: "http://yourapp.com/linkSendSuccess", - linkSendFail: "http://yourapp.com/linkSendFail", - invalidLink: "http://yourapp.com/invalidLink", - invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", - choosePassword: "http://yourapp.com/choosePassword" - } -}) -``` - -## Insecure Options - -When deploying to be production, make sure: - -* `allowClientClassCreation` is set to `false` -* `mountPlayground` is not set to `true` -* `masterKey` is set to a long and complex string -* `readOnlyMasterKey` if set, is set to a long and complex string -* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 -* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) -* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) - -## Using environment variables to configure Parse Server - -You may configure the Parse Server using environment variables: - -```bash -PORT -PARSE_SERVER_APPLICATION_ID -PARSE_SERVER_MASTER_KEY -PARSE_SERVER_DATABASE_URI -PARSE_SERVER_URL -PARSE_SERVER_CLOUD -``` - -The default port is 1337, to use a different port set the PORT environment variable: - -```bash -$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY -``` - -For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 692b704b6..f2d05fe03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1514,12 +1514,45 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "array-back": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", "dev": true }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -2865,12 +2898,28 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "browserslist": { "version": "4.14.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", @@ -2929,6 +2978,27 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3032,6 +3102,24 @@ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "core-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", @@ -3056,6 +3144,12 @@ } } }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", @@ -3069,6 +3163,16 @@ "yaml": "^1.10.0" } }, + "create-thenable": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/create-thenable/-/create-thenable-1.0.2.tgz", + "integrity": "sha1-4gMXIMzJV12M+jH1wUbnYqgMBTQ=", + "dev": true, + "requires": { + "object.omit": "~2.0.0", + "unique-concat": "~0.2.2" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3212,6 +3316,12 @@ "estraverse": "^4.1.1" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -3264,6 +3374,29 @@ "strip-final-newline": "^2.0.0" } }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", + "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "spawn-sync": "^1.0.15", + "tmp": "^0.0.29" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3276,6 +3409,16 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -3305,6 +3448,27 @@ "semver-regex": "^2.0.0" } }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3326,6 +3490,20 @@ "pump": "^3.0.0" } }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -3338,6 +3516,27 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -3380,6 +3579,12 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "hunspell-spellchecker": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hunspell-spellchecker/-/hunspell-spellchecker-1.0.2.tgz", + "integrity": "sha1-oQsL0voAplq2Kkxrc0zkltMYkQ4=", + "dev": true + }, "husky": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz", @@ -3542,6 +3747,52 @@ } } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", + "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "external-editor": "^1.1.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "mute-stream": "0.0.6", + "pinkie-promise": "^2.0.0", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + } + } + }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -3584,6 +3835,21 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", @@ -3614,6 +3880,12 @@ "has-symbols": "^1.0.1" } }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3653,6 +3925,16 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -3746,6 +4028,81 @@ "semver": "^5.6.0" } }, + "markdown-spellcheck": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/markdown-spellcheck/-/markdown-spellcheck-1.3.1.tgz", + "integrity": "sha512-9uyovbDg3Kh2H89VDtqOkXKS9wuRgpLvOHXzPYWMR71tHQZWt2CAf28EIpXNhkFqqoEjXYAx+fXLuKufApYHRQ==", + "dev": true, + "requires": { + "async": "^2.1.4", + "chalk": "^2.0.1", + "commander": "^2.8.1", + "globby": "^6.1.0", + "hunspell-spellchecker": "^1.0.2", + "inquirer": "^1.0.0", + "js-yaml": "^3.10.0", + "marked": "^0.3.5", + "sinon-as-promised": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3773,12 +4130,33 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "mute-stream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", + "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", + "dev": true + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -3800,6 +4178,18 @@ "path-key": "^3.0.0" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -3824,6 +4214,16 @@ "object-keys": "^1.1.1" } }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3848,6 +4248,18 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -3899,6 +4311,12 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3923,6 +4341,21 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -3947,6 +4380,12 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -3972,6 +4411,29 @@ "safe-buffer": "^5.1.0" } }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "rechoir": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", @@ -4086,6 +4548,36 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + }, + "dependencies": { + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + } + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4151,6 +4643,16 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "sinon-as-promised": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/sinon-as-promised/-/sinon-as-promised-4.0.3.tgz", + "integrity": "sha1-wFRbFoX9gTWIpO1pcBJIftEdFRs=", + "dev": true, + "requires": { + "create-thenable": "~1.0.0", + "native-promise-only": "~0.8.1" + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -4163,6 +4665,33 @@ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, "string.prototype.trimend": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", @@ -4183,6 +4712,23 @@ "es-abstract": "^1.18.0-next.1" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -4301,6 +4847,21 @@ } } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, "to-fast-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz", @@ -4319,6 +4880,12 @@ "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "typical": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", @@ -4358,6 +4925,12 @@ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", "dev": true }, + "unique-concat": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/unique-concat/-/unique-concat-0.2.2.tgz", + "integrity": "sha1-khD5vcqsxeHjkpSQ18AZ35bxhxI=", + "dev": true + }, "uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", @@ -4367,6 +4940,12 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "v8-compile-cache": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", diff --git a/package.json b/package.json index b93bcc62b..6dcac46c5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "dev": "npm run dev-webpack & npm run jekyll", "dev-win": "start npm run dev-webpack & start npm run jekyll", "prod": "npm run webpack & npm run jekyll", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "spellcheck" : "mdspell -r *.md && mdspell -r _includes/*/*.md" }, "repository": { "type": "git", @@ -31,7 +32,8 @@ "babel-preset-react": "6.24.1", "husky": "4.3.0", "webpack": "5.1.3", - "webpack-cli": "4.1.0" + "webpack-cli": "4.1.0", + "markdown-spellcheck": "1.3.1" }, "dependencies": { "jquery": "3.5.1", diff --git a/parse-server.md b/parse-server.md index 9b64901f0..4cb98ae1c 100644 --- a/parse-server.md +++ b/parse-server.md @@ -10,6 +10,7 @@ sections: - "parse-server/getting-started.md" - "parse-server/database.md" - "parse-server/usage.md" +- "parse-server/configuration.md" - "parse-server/keys.md" - "parse-server/using-parse-sdks.md" - "parse-server/deploying.md" @@ -30,5 +31,4 @@ sections: - "parse-server/experimental.md" - "parse-server/bleeding-edge.md" - "parse-server/development.md" -- "parse-server/backers.md" --- From f44eb1caefaa729a6bc64696388804f77bc3f2f3 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:27:35 +1100 Subject: [PATCH 12/27] Revert "update" This reverts commit db0fd81388ebabafed79f55e4cbb8816380059f1. --- _includes/parse-server/backers.md | 46 ++ _includes/parse-server/configuration.md | 210 --------- _includes/parse-server/experimental.md | 15 +- _includes/parse-server/usage.md | 213 ++++++++- package-lock.json | 579 ------------------------ package.json | 6 +- parse-server.md | 2 +- 7 files changed, 264 insertions(+), 807 deletions(-) create mode 100644 _includes/parse-server/backers.md delete mode 100644 _includes/parse-server/configuration.md diff --git a/_includes/parse-server/backers.md b/_includes/parse-server/backers.md new file mode 100644 index 000000000..6eb2f25de --- /dev/null +++ b/_includes/parse-server/backers.md @@ -0,0 +1,46 @@ +# Contributors + +This project exists thanks to all the people who contribute... we'd love to see your face on this list! + + + +# Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Backers + +Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer) + + \ No newline at end of file diff --git a/_includes/parse-server/configuration.md b/_includes/parse-server/configuration.md deleted file mode 100644 index 536dc51f1..000000000 --- a/_includes/parse-server/configuration.md +++ /dev/null @@ -1,210 +0,0 @@ -## Configuration - -Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. - -For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). - -## Basic Options - -* `appId`: A unique identifier for your app. -* `databaseURI`: Connection string URI for your MongoDB. -* `cloud`: Path to your app’s Cloud Code. -* `masterKey`: A key that overrides all permissions. Keep this secret. -* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. -* `port`: The default port is 1337, specify this parameter to use a different port. -* `push`: An object containing push configuration. See [Push](#push-notifications) -* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). - -For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. - -The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: - -```js -const express = require('express'); -const ParseServer = require('parse-server').ParseServer; - -const app = express(); -const api = new ParseServer({ ... }); - -// Serve the Parse API at /parse URL prefix -app.use('/parse', api); - -var port = 1337; -app.listen(port, function() { - console.log('parse-server-example running on port ' + port + '.'); -}); -``` - -And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. - -## Additional Options - -### Email verification and password reset - -Verifying user email addresses and enabling password reset via email requires an email adapter. - -You can also use email adapters contributed by the community such as: -- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) -- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) -- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) -- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) -- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) -- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) -- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) -- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) -- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) -- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) - -The Parse Server Configuration Options relating to email verifcation are: - -* `verifyUserEmails`: whether the Parse Server should send mail on user signup -* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for -* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid -* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email -* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. -* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. -* `emailAdapter`: The email adapter. - -```js -const api = ParseServer({ - ...otherOptions, - verifyUserEmails: true, - emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) - preventLoginWithUnverifiedEmail: false, // defaults to false - publicServerURL: 'https://example.com/parse', - appName: 'Parse App', - emailAdapter: { - module: '@parse/simple-mailgun-adapter', - options: { - fromAddress: 'parse@example.com', - domain: 'example.com', - apiKey: 'key-mykey', - } - }, -}); -``` -Note: - -* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. - -### Account Lockout - -Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. - -If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. - -After `duration` minutes of no login attempts, the application will allow the user to try login again. - -*`accountLockout`: Object that contains account lockout rules -*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. -*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. - -```js -const api = ParseServer({ - ...otherOptions, - accountLockout: { - duration: 5, - threshold: 3 - } -}); -``` - -### Password Policy - -Password policy is a good way to enforce that users' passwords are secure. - -Two optional settings can be used to enforce strong passwords. Either one or both can be specified. - -If both are specified, both checks must pass to accept the password - -1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce -2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password - -The full range of options for Password Policy are: - -*`passwordPolicy` is an object that contains the following rules: -*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. -*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords -*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. -*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. -*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) -*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid - -```js -const validatePassword = password => { - if (!password) { - return false; - } - if (password.includes('pass')) { - return false; - } - return true; -} -const api = ParseServer({ - ...otherOptions, - passwordPolicy: { - validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit - validatorCallback: (password) => { return validatePassword(password) }, - validationError: 'Password must contain at least 1 digit.', - doNotAllowUsername: true, - maxPasswordAge: 90, - maxPasswordHistory: 5, - resetTokenValidityDuration: 24*60*60, - resetTokenReuseIfValid: true - } -}); -``` - - -### Custom Pages - -It’s possible to change the default pages of the app and redirect the user to another path or domain. - -```js -const api = ParseServer({ - ...otherOptions, - customPages: { - passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", - verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", - parseFrameURL: "http://yourapp.com/parseFrameURL", - linkSendSuccess: "http://yourapp.com/linkSendSuccess", - linkSendFail: "http://yourapp.com/linkSendFail", - invalidLink: "http://yourapp.com/invalidLink", - invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", - choosePassword: "http://yourapp.com/choosePassword" - } -}) -``` - -## Insecure Options - -When deploying to be production, make sure: - -* `allowClientClassCreation` is set to `false` -* `mountPlayground` is not set to `true` -* `masterKey` is set to a long and complex string -* `readOnlyMasterKey` if set, is set to a long and complex string -* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 -* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) -* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) - -## Using environment variables to configure Parse Server - -You may configure the Parse Server using environment variables: - -```bash -PORT -PARSE_SERVER_APPLICATION_ID -PARSE_SERVER_MASTER_KEY -PARSE_SERVER_DATABASE_URI -PARSE_SERVER_URL -PARSE_SERVER_CLOUD -``` - -The default port is 1337, to use a different port set the PORT environment variable: - -```bash -$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY -``` - -For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 526ced664..023bf2a6e 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -8,14 +8,6 @@ These features may not be approprate for production, so use at your own risk. `directAccess` replaces the HTTP Interface when using the JS SDK in the current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. -Configuration: -```js -const api = new ParseServer({ - //...other configuration - directAccess: true -}); -``` - ## Idempotency This feature deduplicates identical requests that are received by Parse Server mutliple times, typically due to network issues or network adapter access restrictions on mobile operating systems. @@ -26,15 +18,14 @@ This feature needs to be enabled on the client side to send the header and on th Deduplication is only done for object creation and update (`POST` and `PUT` requests). Deduplication is not done for object finding and deletion (`GET` and `DELETE` requests), as these operations are already idempotent by definition. -Configuration: +Configutation: ```js -const api = new ParseServer({ - //...other configuration +let api = new ParseServer({ idempotencyOptions: { paths: [".*"], // enforce for all requests ttl: 120 // keep request IDs for 120s } -}); +} ``` Parameters: diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index da40b75be..d60aa2619 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -12,4 +12,215 @@ const api = new ParseServer({ masterKey: 'mySecretMasterKey', serverURL: 'http://localhost:1337/parse' }); -``` \ No newline at end of file +``` + +## Basic Options + +* `appId`: A unique identifier for your app. +* `databaseURI`: Connection string URI for your MongoDB. +* `cloud`: Path to your app’s Cloud Code. +* `masterKey`: A key that overrides all permissions. Keep this secret. +* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. +* `port`: The default port is 1337, specify this parameter to use a different port. +* `push`: An object containing push configuration. See [Push](#push-notifications) +* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). + +For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. + +The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: + +```js +const express = require('express'); +const ParseServer = require('parse-server').ParseServer; + +const app = express(); +const api = new ParseServer({ ... }); + +// Serve the Parse API at /parse URL prefix +app.use('/parse', api); + +var port = 1337; +app.listen(port, function() { + console.log('parse-server-example running on port ' + port + '.'); +}); +``` + +And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. + +## Configuration + +Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. + +For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). + +## Additional Options + +### Email verification and password reset + +Verifying user email addresses and enabling password reset via email requires an email adapter. + +You can also use email adapters contributed by the community such as: +- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) +- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) +- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) +- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) +- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) +- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) +- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) +- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) +- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) +- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) + +The Parse Server Configuration Options relating to email verifcation are: + +* `verifyUserEmails`: whether the Parse Server should send mail on user signup +* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for +* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid +* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email +* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. +* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. +* `emailAdapter`: The email adapter. + +```js +const api = ParseServer({ + ...otherOptions, + verifyUserEmails: true, + emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) + preventLoginWithUnverifiedEmail: false, // defaults to false + publicServerURL: 'https://example.com/parse', + appName: 'Parse App', + emailAdapter: { + module: '@parse/simple-mailgun-adapter', + options: { + fromAddress: 'parse@example.com', + domain: 'example.com', + apiKey: 'key-mykey', + } + }, +}); +``` +Note: + +* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. + +### Account Lockout + +Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. + +If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. + +After `duration` minutes of no login attempts, the application will allow the user to try login again. + +*`accountLockout`: Object that contains account lockout rules +*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. +*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. + +```js +const api = ParseServer({ + ...otherOptions, + accountLockout: { + duration: 5, + threshold: 3 + } +}); +``` + +### Password Policy + +Password policy is a good way to enforce that users' passwords are secure. + +Two optional settings can be used to enforce strong passwords. Either one or both can be specified. + +If both are specified, both checks must pass to accept the password + +1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce +2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password + +The full range of options for Password Policy are: + +*`passwordPolicy` is an object that contains the following rules: +*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. +*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords +*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. +*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. +*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) +*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid + +```js +const validatePassword = password => { + if (!password) { + return false; + } + if (password.includes('pass')) { + return false; + } + return true; +} +const api = ParseServer({ + ...otherOptions, + passwordPolicy: { + validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit + validatorCallback: (password) => { return validatePassword(password) }, + validationError: 'Password must contain at least 1 digit.', + doNotAllowUsername: true, + maxPasswordAge: 90, + maxPasswordHistory: 5, + resetTokenValidityDuration: 24*60*60, + resetTokenReuseIfValid: true + } +}); +``` + + +### Custom Pages + +It’s possible to change the default pages of the app and redirect the user to another path or domain. + +```js +const api = ParseServer({ + ...otherOptions, + customPages: { + passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", + verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", + parseFrameURL: "http://yourapp.com/parseFrameURL", + linkSendSuccess: "http://yourapp.com/linkSendSuccess", + linkSendFail: "http://yourapp.com/linkSendFail", + invalidLink: "http://yourapp.com/invalidLink", + invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", + choosePassword: "http://yourapp.com/choosePassword" + } +}) +``` + +## Insecure Options + +When deploying to be production, make sure: + +* `allowClientClassCreation` is set to `false` +* `mountPlayground` is not set to `true` +* `masterKey` is set to a long and complex string +* `readOnlyMasterKey` if set, is set to a long and complex string +* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 +* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) +* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) + +## Using environment variables to configure Parse Server + +You may configure the Parse Server using environment variables: + +```bash +PORT +PARSE_SERVER_APPLICATION_ID +PARSE_SERVER_MASTER_KEY +PARSE_SERVER_DATABASE_URI +PARSE_SERVER_URL +PARSE_SERVER_CLOUD +``` + +The default port is 1337, to use a different port set the PORT environment variable: + +```bash +$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` + +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). diff --git a/package-lock.json b/package-lock.json index f2d05fe03..692b704b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1514,45 +1514,12 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, "array-back": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", "dev": true }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -2898,28 +2865,12 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "browserslist": { "version": "4.14.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", @@ -2978,27 +2929,6 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3102,24 +3032,6 @@ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "core-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", @@ -3144,12 +3056,6 @@ } } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", @@ -3163,16 +3069,6 @@ "yaml": "^1.10.0" } }, - "create-thenable": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/create-thenable/-/create-thenable-1.0.2.tgz", - "integrity": "sha1-4gMXIMzJV12M+jH1wUbnYqgMBTQ=", - "dev": true, - "requires": { - "object.omit": "~2.0.0", - "unique-concat": "~0.2.2" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3316,12 +3212,6 @@ "estraverse": "^4.1.1" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -3374,29 +3264,6 @@ "strip-final-newline": "^2.0.0" } }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "external-editor": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "spawn-sync": "^1.0.15", - "tmp": "^0.0.29" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3409,16 +3276,6 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -3448,27 +3305,6 @@ "semver-regex": "^2.0.0" } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3490,20 +3326,6 @@ "pump": "^3.0.0" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -3516,27 +3338,6 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -3579,12 +3380,6 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, - "hunspell-spellchecker": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hunspell-spellchecker/-/hunspell-spellchecker-1.0.2.tgz", - "integrity": "sha1-oQsL0voAplq2Kkxrc0zkltMYkQ4=", - "dev": true - }, "husky": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz", @@ -3747,52 +3542,6 @@ } } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "external-editor": "^1.1.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "mute-stream": "0.0.6", - "pinkie-promise": "^2.0.0", - "run-async": "^2.2.0", - "rx": "^4.1.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - } - } - }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -3835,21 +3584,6 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", @@ -3880,12 +3614,6 @@ "has-symbols": "^1.0.1" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3925,16 +3653,6 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4028,81 +3746,6 @@ "semver": "^5.6.0" } }, - "markdown-spellcheck": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/markdown-spellcheck/-/markdown-spellcheck-1.3.1.tgz", - "integrity": "sha512-9uyovbDg3Kh2H89VDtqOkXKS9wuRgpLvOHXzPYWMR71tHQZWt2CAf28EIpXNhkFqqoEjXYAx+fXLuKufApYHRQ==", - "dev": true, - "requires": { - "async": "^2.1.4", - "chalk": "^2.0.1", - "commander": "^2.8.1", - "globby": "^6.1.0", - "hunspell-spellchecker": "^1.0.2", - "inquirer": "^1.0.0", - "js-yaml": "^3.10.0", - "marked": "^0.3.5", - "sinon-as-promised": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4130,33 +3773,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "mute-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", - "dev": true - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", - "dev": true - }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -4178,18 +3800,6 @@ "path-key": "^3.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -4214,16 +3824,6 @@ "object-keys": "^1.1.1" } }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4248,18 +3848,6 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4311,12 +3899,6 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4341,21 +3923,6 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -4380,12 +3947,6 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -4411,29 +3972,6 @@ "safe-buffer": "^5.1.0" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "rechoir": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", @@ -4548,36 +4086,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - }, - "dependencies": { - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - } - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4643,16 +4151,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "sinon-as-promised": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/sinon-as-promised/-/sinon-as-promised-4.0.3.tgz", - "integrity": "sha1-wFRbFoX9gTWIpO1pcBJIftEdFRs=", - "dev": true, - "requires": { - "create-thenable": "~1.0.0", - "native-promise-only": "~0.8.1" - } - }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -4665,33 +4163,6 @@ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, "string.prototype.trimend": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", @@ -4712,23 +4183,6 @@ "es-abstract": "^1.18.0-next.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -4847,21 +4301,6 @@ } } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, "to-fast-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz", @@ -4880,12 +4319,6 @@ "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", "dev": true }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typical": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", @@ -4925,12 +4358,6 @@ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", "dev": true }, - "unique-concat": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/unique-concat/-/unique-concat-0.2.2.tgz", - "integrity": "sha1-khD5vcqsxeHjkpSQ18AZ35bxhxI=", - "dev": true - }, "uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", @@ -4940,12 +4367,6 @@ "punycode": "^2.1.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "v8-compile-cache": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", diff --git a/package.json b/package.json index 6dcac46c5..b93bcc62b 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "dev": "npm run dev-webpack & npm run jekyll", "dev-win": "start npm run dev-webpack & start npm run jekyll", "prod": "npm run webpack & npm run jekyll", - "test": "echo \"Error: no test specified\" && exit 1", - "spellcheck" : "mdspell -r *.md && mdspell -r _includes/*/*.md" + "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", @@ -32,8 +31,7 @@ "babel-preset-react": "6.24.1", "husky": "4.3.0", "webpack": "5.1.3", - "webpack-cli": "4.1.0", - "markdown-spellcheck": "1.3.1" + "webpack-cli": "4.1.0" }, "dependencies": { "jquery": "3.5.1", diff --git a/parse-server.md b/parse-server.md index 4cb98ae1c..9b64901f0 100644 --- a/parse-server.md +++ b/parse-server.md @@ -10,7 +10,6 @@ sections: - "parse-server/getting-started.md" - "parse-server/database.md" - "parse-server/usage.md" -- "parse-server/configuration.md" - "parse-server/keys.md" - "parse-server/using-parse-sdks.md" - "parse-server/deploying.md" @@ -31,4 +30,5 @@ sections: - "parse-server/experimental.md" - "parse-server/bleeding-edge.md" - "parse-server/development.md" +- "parse-server/backers.md" --- From 10a3a45ef44bc16199374592ffb18ab2400e4184 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 2 Feb 2021 12:31:14 +1100 Subject: [PATCH 13/27] recommit --- _includes/parse-server/backers.md | 46 ----- _includes/parse-server/configuration.md | 210 +++++++++++++++++++++++ _includes/parse-server/experimental.md | 13 +- _includes/parse-server/usage.md | 213 +----------------------- parse-server.md | 1 - 5 files changed, 222 insertions(+), 261 deletions(-) delete mode 100644 _includes/parse-server/backers.md create mode 100644 _includes/parse-server/configuration.md diff --git a/_includes/parse-server/backers.md b/_includes/parse-server/backers.md deleted file mode 100644 index 6eb2f25de..000000000 --- a/_includes/parse-server/backers.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributors - -This project exists thanks to all the people who contribute... we'd love to see your face on this list! - - - -# Sponsors - -Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor!](https://opencollective.com/parse-server#sponsor) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Backers - -Support us with a monthly donation and help us continue our activities. [Become a backer!](https://opencollective.com/parse-server#backer) - - \ No newline at end of file diff --git a/_includes/parse-server/configuration.md b/_includes/parse-server/configuration.md new file mode 100644 index 000000000..536dc51f1 --- /dev/null +++ b/_includes/parse-server/configuration.md @@ -0,0 +1,210 @@ +## Configuration + +Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. + +For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). + +## Basic Options + +* `appId`: A unique identifier for your app. +* `databaseURI`: Connection string URI for your MongoDB. +* `cloud`: Path to your app’s Cloud Code. +* `masterKey`: A key that overrides all permissions. Keep this secret. +* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. +* `port`: The default port is 1337, specify this parameter to use a different port. +* `push`: An object containing push configuration. See [Push](#push-notifications) +* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). + +For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. + +The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: + +```js +const express = require('express'); +const ParseServer = require('parse-server').ParseServer; + +const app = express(); +const api = new ParseServer({ ... }); + +// Serve the Parse API at /parse URL prefix +app.use('/parse', api); + +var port = 1337; +app.listen(port, function() { + console.log('parse-server-example running on port ' + port + '.'); +}); +``` + +And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. + +## Additional Options + +### Email verification and password reset + +Verifying user email addresses and enabling password reset via email requires an email adapter. + +You can also use email adapters contributed by the community such as: +- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) +- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) +- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) +- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) +- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) +- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) +- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) +- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) +- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) +- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) + +The Parse Server Configuration Options relating to email verifcation are: + +* `verifyUserEmails`: whether the Parse Server should send mail on user signup +* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for +* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid +* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email +* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. +* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. +* `emailAdapter`: The email adapter. + +```js +const api = ParseServer({ + ...otherOptions, + verifyUserEmails: true, + emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) + preventLoginWithUnverifiedEmail: false, // defaults to false + publicServerURL: 'https://example.com/parse', + appName: 'Parse App', + emailAdapter: { + module: '@parse/simple-mailgun-adapter', + options: { + fromAddress: 'parse@example.com', + domain: 'example.com', + apiKey: 'key-mykey', + } + }, +}); +``` +Note: + +* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. + +### Account Lockout + +Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. + +If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. + +After `duration` minutes of no login attempts, the application will allow the user to try login again. + +*`accountLockout`: Object that contains account lockout rules +*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. +*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. + +```js +const api = ParseServer({ + ...otherOptions, + accountLockout: { + duration: 5, + threshold: 3 + } +}); +``` + +### Password Policy + +Password policy is a good way to enforce that users' passwords are secure. + +Two optional settings can be used to enforce strong passwords. Either one or both can be specified. + +If both are specified, both checks must pass to accept the password + +1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce +2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password + +The full range of options for Password Policy are: + +*`passwordPolicy` is an object that contains the following rules: +*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. +*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords +*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. +*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. +*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) +*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid + +```js +const validatePassword = password => { + if (!password) { + return false; + } + if (password.includes('pass')) { + return false; + } + return true; +} +const api = ParseServer({ + ...otherOptions, + passwordPolicy: { + validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit + validatorCallback: (password) => { return validatePassword(password) }, + validationError: 'Password must contain at least 1 digit.', + doNotAllowUsername: true, + maxPasswordAge: 90, + maxPasswordHistory: 5, + resetTokenValidityDuration: 24*60*60, + resetTokenReuseIfValid: true + } +}); +``` + + +### Custom Pages + +It’s possible to change the default pages of the app and redirect the user to another path or domain. + +```js +const api = ParseServer({ + ...otherOptions, + customPages: { + passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", + verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", + parseFrameURL: "http://yourapp.com/parseFrameURL", + linkSendSuccess: "http://yourapp.com/linkSendSuccess", + linkSendFail: "http://yourapp.com/linkSendFail", + invalidLink: "http://yourapp.com/invalidLink", + invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", + choosePassword: "http://yourapp.com/choosePassword" + } +}) +``` + +## Insecure Options + +When deploying to be production, make sure: + +* `allowClientClassCreation` is set to `false` +* `mountPlayground` is not set to `true` +* `masterKey` is set to a long and complex string +* `readOnlyMasterKey` if set, is set to a long and complex string +* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 +* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) +* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) + +## Using environment variables to configure Parse Server + +You may configure the Parse Server using environment variables: + +```bash +PORT +PARSE_SERVER_APPLICATION_ID +PARSE_SERVER_MASTER_KEY +PARSE_SERVER_DATABASE_URI +PARSE_SERVER_URL +PARSE_SERVER_CLOUD +``` + +The default port is 1337, to use a different port set the PORT environment variable: + +```bash +$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` + +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 023bf2a6e..ae6b15536 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -8,6 +8,14 @@ These features may not be approprate for production, so use at your own risk. `directAccess` replaces the HTTP Interface when using the JS SDK in the current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. +Configuration: +```js +const api = new ParseServer({ + //...other configuration + directAccess: true +}); +``` + ## Idempotency This feature deduplicates identical requests that are received by Parse Server mutliple times, typically due to network issues or network adapter access restrictions on mobile operating systems. @@ -20,12 +28,13 @@ Deduplication is only done for object creation and update (`POST` and `PUT` requ Configutation: ```js -let api = new ParseServer({ +const api = new ParseServer({ + //...other configuration idempotencyOptions: { paths: [".*"], // enforce for all requests ttl: 120 // keep request IDs for 120s } -} +}); ``` Parameters: diff --git a/_includes/parse-server/usage.md b/_includes/parse-server/usage.md index d60aa2619..da40b75be 100644 --- a/_includes/parse-server/usage.md +++ b/_includes/parse-server/usage.md @@ -12,215 +12,4 @@ const api = new ParseServer({ masterKey: 'mySecretMasterKey', serverURL: 'http://localhost:1337/parse' }); -``` - -## Basic Options - -* `appId`: A unique identifier for your app. -* `databaseURI`: Connection string URI for your MongoDB. -* `cloud`: Path to your app’s Cloud Code. -* `masterKey`: A key that overrides all permissions. Keep this secret. -* `serverURL`: URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code. -* `port`: The default port is 1337, specify this parameter to use a different port. -* `push`: An object containing push configuration. See [Push](#push-notifications) -* `auth`: Configure support for [3rd party authentication](#oauth-and-3rd-party-authentication). - -For the full list of available options, run `parse-server --help` or refer to [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for a complete list of configuration options. - -The Parse Server object was built to be passed directly into `app.use`, which will mount the Parse API at a specified path in your Express app: - -```js -const express = require('express'); -const ParseServer = require('parse-server').ParseServer; - -const app = express(); -const api = new ParseServer({ ... }); - -// Serve the Parse API at /parse URL prefix -app.use('/parse', api); - -var port = 1337; -app.listen(port, function() { - console.log('parse-server-example running on port ' + port + '.'); -}); -``` - -And with that, you will have a Parse Server running on port 1337, serving the Parse API at `/parse`. - -## Configuration - -Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. - -For the full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html). - -## Additional Options - -### Email verification and password reset - -Verifying user email addresses and enabling password reset via email requires an email adapter. - -You can also use email adapters contributed by the community such as: -- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) -- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) -- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) -- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) -- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) -- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) -- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) -- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) -- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) -- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) - -The Parse Server Configuration Options relating to email verifcation are: - -* `verifyUserEmails`: whether the Parse Server should send mail on user signup -* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for -* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid -* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email -* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. -* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. -* `emailAdapter`: The email adapter. - -```js -const api = ParseServer({ - ...otherOptions, - verifyUserEmails: true, - emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds) - preventLoginWithUnverifiedEmail: false, // defaults to false - publicServerURL: 'https://example.com/parse', - appName: 'Parse App', - emailAdapter: { - module: '@parse/simple-mailgun-adapter', - options: { - fromAddress: 'parse@example.com', - domain: 'example.com', - apiKey: 'key-mykey', - } - }, -}); -``` -Note: - -* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. - -### Account Lockout - -Account lockouts prevent login requests after a defined number of failed password attempts. The account lock prevents logging in for a period of time even if the correct password is entered. - -If the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after minute(s)`. - -After `duration` minutes of no login attempts, the application will allow the user to try login again. - -*`accountLockout`: Object that contains account lockout rules -*`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. -*`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. - -```js -const api = ParseServer({ - ...otherOptions, - accountLockout: { - duration: 5, - threshold: 3 - } -}); -``` - -### Password Policy - -Password policy is a good way to enforce that users' passwords are secure. - -Two optional settings can be used to enforce strong passwords. Either one or both can be specified. - -If both are specified, both checks must pass to accept the password - -1. `passwordPolicy.validatorPattern`: a RegExp object or a regex string representing the pattern to enforce -2. `passwordPolicy.validatorCallback`: a callback function to be invoked to validate the password - -The full range of options for Password Policy are: - -*`passwordPolicy` is an object that contains the following rules: -*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. -*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords -*`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. -*`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. -*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) -*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid - -```js -const validatePassword = password => { - if (!password) { - return false; - } - if (password.includes('pass')) { - return false; - } - return true; -} -const api = ParseServer({ - ...otherOptions, - passwordPolicy: { - validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit - validatorCallback: (password) => { return validatePassword(password) }, - validationError: 'Password must contain at least 1 digit.', - doNotAllowUsername: true, - maxPasswordAge: 90, - maxPasswordHistory: 5, - resetTokenValidityDuration: 24*60*60, - resetTokenReuseIfValid: true - } -}); -``` - - -### Custom Pages - -It’s possible to change the default pages of the app and redirect the user to another path or domain. - -```js -const api = ParseServer({ - ...otherOptions, - customPages: { - passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", - verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", - parseFrameURL: "http://yourapp.com/parseFrameURL", - linkSendSuccess: "http://yourapp.com/linkSendSuccess", - linkSendFail: "http://yourapp.com/linkSendFail", - invalidLink: "http://yourapp.com/invalidLink", - invalidVerificationLink: "http://yourapp.com/invalidVerificationLink", - choosePassword: "http://yourapp.com/choosePassword" - } -}) -``` - -## Insecure Options - -When deploying to be production, make sure: - -* `allowClientClassCreation` is set to `false` -* `mountPlayground` is not set to `true` -* `masterKey` is set to a long and complex string -* `readOnlyMasterKey` if set, is set to a long and complex string -* That you have authentication required on your database, and, if you are using mongo, disable unauthenticated access to port 27017 -* You have restricted `count` and `addField` operations via [Class Level Permissions](#class-level-permissions) -* You enforce ACL and data validation using [cloud code]({{site.baseURL}}/cloudcode/guide/) - -## Using environment variables to configure Parse Server - -You may configure the Parse Server using environment variables: - -```bash -PORT -PARSE_SERVER_APPLICATION_ID -PARSE_SERVER_MASTER_KEY -PARSE_SERVER_DATABASE_URI -PARSE_SERVER_URL -PARSE_SERVER_CLOUD -``` - -The default port is 1337, to use a different port set the PORT environment variable: - -```bash -$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY -``` - -For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). +``` \ No newline at end of file diff --git a/parse-server.md b/parse-server.md index 9b64901f0..7e55980bc 100644 --- a/parse-server.md +++ b/parse-server.md @@ -30,5 +30,4 @@ sections: - "parse-server/experimental.md" - "parse-server/bleeding-edge.md" - "parse-server/development.md" -- "parse-server/backers.md" --- From d1b34ce08c3beb9fd18ca4f0c471e7e9e67f364e Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 3 Feb 2021 00:15:47 +1100 Subject: [PATCH 14/27] Update _includes/parse-server/logging.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/parse-server/logging.md b/_includes/parse-server/logging.md index 8df4cf311..a25ca724d 100644 --- a/_includes/parse-server/logging.md +++ b/_includes/parse-server/logging.md @@ -13,7 +13,7 @@ Set the `VERBOSE` environment variable when starting `parse-server`. **Want logs to be in placed in a different folder?** -Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. Usage :- `PARSE_SERVER_LOGS_FOLDER='' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` +Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. ```PARSE_SERVER_LOGS_FOLDER='' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY``` **Want to log specific levels?** From 4b912f671c149ec4fe6f63d208ef18d60576dcbd Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 10 Feb 2021 20:45:15 +0000 Subject: [PATCH 15/27] fix formatting --- _includes/parse-server/configuration.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/_includes/parse-server/configuration.md b/_includes/parse-server/configuration.md index 536dc51f1..0564ecede 100644 --- a/_includes/parse-server/configuration.md +++ b/_includes/parse-server/configuration.md @@ -1,4 +1,4 @@ -## Configuration +# Configuration Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options. @@ -155,7 +155,6 @@ const api = ParseServer({ }); ``` - ### Custom Pages It’s possible to change the default pages of the app and redirect the user to another path or domain. @@ -207,4 +206,4 @@ The default port is 1337, to use a different port set the PORT environment varia $ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY ``` -For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). \ No newline at end of file +For the full list of configurable environment variables, run `parse-server --help` or take a look at [Parse Server Configuration](https://github.com/parse-community/parse-server/blob/master/src/Options/Definitions.js). From eb715a332ecb0d45cb5b6527a3c33a9f2a34fabc Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 10 Feb 2021 21:32:01 +0000 Subject: [PATCH 16/27] reordering email section & nits --- _includes/parse-server/configuration.md | 64 ++++++++++++------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/_includes/parse-server/configuration.md b/_includes/parse-server/configuration.md index 0564ecede..e803788af 100644 --- a/_includes/parse-server/configuration.md +++ b/_includes/parse-server/configuration.md @@ -41,29 +41,7 @@ And with that, you will have a Parse Server running on port 1337, serving the Pa ### Email verification and password reset -Verifying user email addresses and enabling password reset via email requires an email adapter. - -You can also use email adapters contributed by the community such as: -- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) -- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) -- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) -- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) -- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) -- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) -- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) -- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) -- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) -- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) - -The Parse Server Configuration Options relating to email verifcation are: - -* `verifyUserEmails`: whether the Parse Server should send mail on user signup -* `emailVerifyTokenValidityDuration`: how long the email verify tokens should be valid for -* `emailVerifyTokenReuseIfValid`: whether an existing token should be resent if the token is still valid -* `preventLoginWithUnverifiedEmail`: whether the Parse Server should prevent login until the user verifies their email -* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. -* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. -* `emailAdapter`: The email adapter. +Verifying user email addresses and enabling password reset via email requires an email adapter. As part of the parse-server package we provide an adapter for sending email through Mailgun. To use it, sign up for Mailgun, and add this to your initialization code: ```js const api = ParseServer({ @@ -83,9 +61,33 @@ const api = ParseServer({ }, }); ``` + +You can also use email adapters contributed by the community such as: +- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template) +- [parse-smtp-template (Multi Language and Multi Template)](https://www.npmjs.com/package/parse-smtp-template) +- [parse-server-postmark-adapter](https://www.npmjs.com/package/parse-server-postmark-adapter) +- [parse-server-sendgrid-adapter](https://www.npmjs.com/package/parse-server-sendgrid-adapter) +- [parse-server-mandrill-adapter](https://www.npmjs.com/package/parse-server-mandrill-adapter) +- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter) +- [parse-server-sendinblue-adapter](https://www.npmjs.com/package/parse-server-sendinblue-adapter) +- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter) +- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter) +- [parse-server-generic-email-adapter](https://www.npmjs.com/package/parse-server-generic-email-adapter) + +The Parse Server Configuration Options relating to email verifcation are: + +* `verifyUserEmails`: Whether the Parse Server should send an email on user signup. +* `emailVerifyTokenValidityDuration`: How long the email verify tokens should be valid for. +* `emailVerifyTokenReuseIfValid`: Whether an existing token should be resent if the token is still valid. +* `preventLoginWithUnverifiedEmail`: Whether the Parse Server should prevent login until the user verifies their email. +* `publicServerURL`: The public URL of your app. This will appear in the link that is used to verify email addresses and reset passwords. +* `appName`: Your apps name. This will appear in the subject and body of the emails that are sent. +* `emailAdapter`: The email adapter. + + Note: -* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then email verify token never expires. Else, email verify token expires after `emailVerifyTokenValidityDuration`. +* If `verifyUserEmails` is `true` and if `emailVerifyTokenValidityDuration` is `undefined` then the email verify token never expires. Otherwise, the email verify token expires after `emailVerifyTokenValidityDuration`. ### Account Lockout @@ -95,7 +97,7 @@ If the account lockout policy is set and there are more than `threshold` number After `duration` minutes of no login attempts, the application will allow the user to try login again. -*`accountLockout`: Object that contains account lockout rules +*`accountLockout`: Object that contains account lockout rules. *`accountLockout.duration`: Determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000. *`accountLockout.threshold`: Determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000. @@ -111,9 +113,7 @@ const api = ParseServer({ ### Password Policy -Password policy is a good way to enforce that users' passwords are secure. - -Two optional settings can be used to enforce strong passwords. Either one or both can be specified. +Password policy is a good way to enforce that users' passwords are secure. Two optional settings can be used to enforce strong passwords. Either one or both can be specified. If both are specified, both checks must pass to accept the password @@ -123,12 +123,12 @@ If both are specified, both checks must pass to accept the password The full range of options for Password Policy are: *`passwordPolicy` is an object that contains the following rules: -*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements." message. -*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords +*`passwordPolicy.validationError`: optional error message to be sent instead of the default "Password does not meet the Password Policy requirements" message. +*`passwordPolicy.doNotAllowUsername`: optional setting to disallow username in passwords. *`passwordPolicy.maxPasswordAge`: optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/last reset. *`passwordPolicy.maxPasswordHistory`: optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or specifying 0 will not enforce history. -*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds) -*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend current token if it's still valid +*`passwordPolicy.resetTokenValidityDuration`: optional setting to set a validity duration for password reset links (in seconds). +*`passwordPolicy.resetTokenReuseIfValid`: optional setting to resend the current token if it's still valid. ```js const validatePassword = password => { From eec4cdb4ab4d85e7786b8cafb099c69e2fb8e589 Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 10 Feb 2021 21:37:09 +0000 Subject: [PATCH 17/27] nits --- _includes/parse-server/logging.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/_includes/parse-server/logging.md b/_includes/parse-server/logging.md index a25ca724d..511bf17ec 100644 --- a/_includes/parse-server/logging.md +++ b/_includes/parse-server/logging.md @@ -9,16 +9,31 @@ Logs are also viewable in the Parse Dashboard. **Want to log each request and response?** Set the `VERBOSE` environment variable when starting `parse-server`. -```VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY``` + +``` +VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` **Want logs to be in placed in a different folder?** -Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. ```PARSE_SERVER_LOGS_FOLDER='' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY``` +Pass the `PARSE_SERVER_LOGS_FOLDER` environment variable when starting `parse-server`. + +``` +PARSE_SERVER_LOGS_FOLDER='' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` **Want to log specific levels?** -Pass the `logLevel` parameter when starting `parse-server`. Usage :- `parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL` +Pass the `logLevel` parameter when starting `parse-server`. + +``` +parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL +``` **Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)?** -Pass the `JSON_LOGS` environment variable when starting `parse-server`. Usage :- `JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY` +Pass the `JSON_LOGS` environment variable when starting `parse-server`. + +``` +JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY +``` From e7437c3f77fe29256dc494c7a51c4d7ea8489c70 Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 10 Feb 2021 21:39:13 +0000 Subject: [PATCH 18/27] nit --- _includes/parse-server/experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index ae6b15536..4811b16e2 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -54,6 +54,6 @@ Parameters: * `idempotencyOptions.ttl`: The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. -#### Notes +### Notes - This feature is currently only available for MongoDB and not for Postgres. From 198c56818efa960265698f5477e72108937154d3 Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 10 Feb 2021 21:49:59 +0000 Subject: [PATCH 19/27] remove migration guide contents --- _includes/parse-server/upgrading-to-v3.md | 244 +--------------------- 1 file changed, 2 insertions(+), 242 deletions(-) diff --git a/_includes/parse-server/upgrading-to-v3.md b/_includes/parse-server/upgrading-to-v3.md index d3e8d84c3..ea8172a29 100644 --- a/_includes/parse-server/upgrading-to-v3.md +++ b/_includes/parse-server/upgrading-to-v3.md @@ -1,245 +1,5 @@ # Upgrading Parse Server to version 3.0.0 -Starting 3.0.0, parse-server uses the JS SDK version 2.0. -In short, parse SDK v2.0 removes the backbone style callbacks as well as the Parse.Promise object in favor of native promises. -All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution. - -With the 3.0.0, parse-server has completely revamped it's cloud code interface. Gone are the backbone style calls, welcome promises and async/await. - -In order to leverage those new nodejs features, you'll need to run at least node 8.10. We've dropped the support of node 6 a few months ago, so if you haven't made the change yet, now would be a really good time to take the plunge. - -## Migrating Cloud Code Hooks - -### Synchronous validations: - -```js -// before -Parse.Cloud.beforeSave('MyClassName', function(request, response) { - if (!passesValidation(request.object)) { - response.error('Ooops something went wrong'); - } else { - response.success(); - } -}); - -// after -Parse.Cloud.beforeSave('MyClassName', (request) => { - if (!passesValidation(request.object)) { - throw new Parse.Error('Ooops something went wrong'); - } -}); -``` - -```js -// before -Parse.Cloud.define('doFunction', function(request, response) { - if (!passesValidation(request.user)) { - response.error('Ooops something went wrong'); - } else { - response.success('Do function ran successfully'); - } -}); - -// after -Parse.Cloud.define('doFunction', (request) => { - if (!passesValidation(request.user)) { - throw new Parse.Error('Ooops something went wrong'); - } - return 'Do function ran successfully'; -}); -``` - -All methods are wrapped in promises, so you can freely `throw` `Error`, `Parse.Error` or `string` to mark an error. - -### Asynchronous validations: - -For asynchronous code, you can use promises or async / await. - -Consider the following beforeSave call that would replace the contents of a fileURL with a proper copy of the image as a Parse.File. - -```js -// before -Parse.Cloud.beforeSave('Post', function(request, response) { - Parse.Cloud.httpRequest({ - url: request.object.get('fileURL'), - success: function(contents) { - const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); - file.save({ - success: function() { - request.object.set('file', file); - response.success(); - }, - error: response.error - }); - }, - error: response.error - }); -}); -``` - -As we can see the current way, with backbone style callbacks is quite tough to read and maintain. -It's also not really trivial to handle errors, as you need to pass the response.error to each error handlers. - -Now it can be modernized with promises: - -```js -// after (with promises) -Parse.Cloud.beforeSave('MyClassName', (request) => { - return Parse.Cloud.httpRequest({ - url: request.object.get('fileURL') - }).then((contents) => { - const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); - request.object.set('file', file); - return file.save(); - }); -}); -``` - -And you can even make it better with async/await. - -```js -// after with async/await -Parse.Cloud.beforeSave('MyClassName', async (request) => { - const contents = await Parse.Cloud.httpRequest({ - url: request.object.get('fileURL') - }); - - const file = new Parse.File('image.png', { base64: contents.buffer.toString('base64') }); - request.object.set('file', file); - await file.save(); -}); -``` - -## Aborting hooks and functions - -In order to abort a `beforeSave` or any hook, you now need to throw an error: - -```js -// after with async/await -Parse.Cloud.beforeSave('MyClassName', async (request) => { - // valid, will result in a Parse.Error(SCRIPT_FAILED, 'Something went wrong') - throw 'Something went wrong' - // also valid, will fail with Parse.Error(SCRIPT_FAILED, 'Something else went wrong') - throw new Error('Something else went wrong') - // using a Parse.Error is also valid - throw new Parse.Error(1001, 'My error') -}); -``` - -## Migrating Functions - -Cloud Functions can be migrated the same way as cloud code hooks. -In functions, the response object is not passed anymore, as it is in cloud code hooks - -Continuing with the image downloader example: - -```js -// before -Parse.Cloud.define('downloadImage', function(request, response) { - Parse.Cloud.httpRequest({ - url: request.params.url, - success: function(contents) { - const file = new Parse.File(request.params.name, { base64: contents.buffer.toString('base64') }); - file.save({ - success: function() { - response.success(file); - }, - error: response.error - }); - }, - error: response.error - }); -}); -``` - -You would call this method with: - -```js -Parse.Cloud.run('downloadImage',{ - url: 'https://example.com/file', - name: 'my-file' -}); -``` - -To migrate this function you would follow the same practices as the ones before, and we'll jump right away to the async/await implementation: - -```js -// after with async/await -Parse.Cloud.define('downloadImage', async (request) => { - const { - url, name - } = request.params; - const response = await Parse.Cloud.httpRequest({ url }); - - const file = new Parse.File(name, { base64: response.buffer.toString('base64') }); - await file.save(); - return file; -}); -``` - -## Migrating jobs - -As with hooks and functions, jobs don't have a status object anymore. -The message method has been moved to the request object. - -```js -// before -Parse.Cloud.job('downloadImageJob', function(request, status) { - var query = new Parse.Query('ImagesToDownload'); - query.find({ - success: function(images) { - var done = 0; - for (var i = 0; i { - request.message('Doing ' + image.get('url')); - const contents = await Parse.Cloud.httpRequest({ - url: image.get('url') - }); - request.message('Got ' + image.get('url')); - const file = new Parse.File(image.get('name'), { base64: contents.buffer.toString('base64') }); - await file.save(); - request.message('Saved ' + image.get('url')); - }); - await Promise.all(promises); -}); - -``` - -As you can see the new implementation is more concise, easier to read and maintain. +Starting 3.0.0, Parse Server uses the JS SDK version 2.0. In short, Parse SDK v2.0 removes the backbone style callbacks as well as the `Parse.Promise` object in favor of native promises. All the Cloud Code interfaces also have been updated to reflect those changes, and all backbone style response objects are removed and replaced by Promise style resolution. +We have written up a [migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md), hoping this will help you transition to the next major release. From 6e843fc937020889c63f1b8afcefea449000a457 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 11 Feb 2021 11:35:16 +1100 Subject: [PATCH 20/27] Update _includes/parse-server/experimental.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/parse-server/experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 4811b16e2..cf1166169 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -1,6 +1,6 @@ # Experimental -Experimental Features are items that we are experimenting with that will eventually become full features, or items that will be removed from the system if they are proven to not work well. +Experimental features may not be 'battle tested' and could have issues. They are likely to become full features at some point, but could be removed from the system if they are problematic. These features may not be approprate for production, so use at your own risk. From 2369495de689096c61f473a1540d07cfddfb7539 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 11 Feb 2021 11:37:10 +1100 Subject: [PATCH 21/27] Update _includes/common/security.md Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- _includes/common/security.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/_includes/common/security.md b/_includes/common/security.md index fb5bd7a61..989dc32b6 100644 --- a/_includes/common/security.md +++ b/_includes/common/security.md @@ -551,9 +551,6 @@ Parse.Cloud.define("like", async request => { post.id = request.params.postId; post.increment("likes"); await post.save(null, { useMasterKey: true }) -}, { - fields: ['postId'], - requireUser: true }); ``` From 6c6f032a7a3f3a46e87f20ec31b9587876b63b06 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 11 Feb 2021 11:46:58 +1100 Subject: [PATCH 22/27] add localization --- _includes/parse-server/experimental.md | 166 ++++++++++++++- _includes/parse-server/graphql.md | 273 ------------------------- 2 files changed, 165 insertions(+), 274 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index cf1166169..cc6cf70d2 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -20,7 +20,7 @@ const api = new ParseServer({ This feature deduplicates identical requests that are received by Parse Server mutliple times, typically due to network issues or network adapter access restrictions on mobile operating systems. -Identical requests are identified by their request header `X-Parse-Request-Id`. Therefore a client request has to include this header for deduplication to be applied. Requests that do not contain this header cannot be deduplicated and are processed normally by Parse Server. This means rolling out this feature to clients is seamless as Parse Server still processes request without this header when this feature is enbabled. +Identical requests are identified by their request header `X-Parse-Request-Id`. Therefore a client request has to include this header for deduplication to be applied. Requests that do not contain this header cannot be deduplicated and are processed normally by Parse Server. This means rolling out this feature to clients is seamless as Parse Server still processes requests without this header when this feature is enabled. This feature needs to be enabled on the client side to send the header and on the server to process the header. Refer to the specific Parse SDK docs to see whether the feature is supported yet. @@ -57,3 +57,167 @@ Parameters: ### Notes - This feature is currently only available for MongoDB and not for Postgres. + +## Localization + +### Pages +**Caution, this is an experimental feature that may not be appropriate for production.** + +Custom pages as well as feature pages (e.g. password reset, email verification) can be localized with the `pages` option in the Parse Server configuration: + +```js +const api = new ParseServer({ + ...otherOptions, + pages: { + enableRouter: true, // Enables the experimental feature; required for localization + enableLocalization: true, + } +} +``` +Localization is achieved by matching a request-supplied `locale` parameter with localized page content. The locale can be supplied in either the request query, body or header with the following keys: +- query: `locale` +- body: `locale` +- header: `x-parse-page-param-locale` +For example, a password reset link with the locale parameter in the query could look like this: +``` +http://example.com/parse/apps/[appId]/request_password_reset?token=[token]&username=[username]&locale=de-AT +``` +- Localization is only available for pages in the pages directory as set with `pages.pagesPath`. +- Localization for feature pages (e.g. password reset, email verification) is disabled if `pages.customUrls` are set, even if the custom URLs point to the pages within the pages path. +- Only `.html` files are considered for localization when localizing custom pages. +Pages can be localized in two ways: +#### Localization with Directory Structure +Pages are localized by using the corresponding file in the directory structure where the files are placed in subdirectories named after the locale or language. The file in the base directory is the default file. +**Example Directory Structure:** +```js +root/ +├── public/ // pages base path +│ ├── example.html // default file +│ └── de/ // de language folder +│ │ └── example.html // de localized file +│ └── de-AT/ // de-AT locale folder +│ │ └── example.html // de-AT localized file +``` +Files are matched with the locale in the following order: +1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`. +1. Language match, e.g. locale `de-CH` matches file in folder `de`. +1. Default; file in base folder is returned. +**Configuration Example:** +```js +const api = new ParseServer({ + ...otherOptions, + pages: { + enableRouter: true, // Enables the experimental feature; required for localization + enableLocalization: true, + customUrls: { + passwordReset: 'https://example.com/page.html' + } + } +} +``` +Pros: +- All files are complete in their content and can be easily opened and previewed by viewing the file in a browser. +Cons: +- In most cases, a localized page differs only slighly from the default page, which could cause a lot of duplicate code that is difficult to maintain. +#### Localization with JSON Resource +Pages are localized by adding placeholders in the HTML files and providing a JSON resource that contains the translations to fill into the placeholders. +**Example Directory Structure:** +```js +root/ +├── public/ // pages base path +│ ├── example.html // the page containing placeholders +├── private/ // folder outside of public scope +│ └── translations.json // JSON resource file +``` +The JSON resource file loosely follows the [i18next](https://github.com/i18next/i18next) syntax, which is a syntax that is often supported by translation platforms, making it easy to manage translations, exporting them for use in Parse Server, and even to automate this workflow. +**Example JSON Content:** +```json +{ + "en": { // resource for language `en` (English) + "translation": { + "greeting": "Hello!" + } + }, + "de": { // resource for language `de` (German) + "translation": { + "greeting": "Hallo!" + } + } + "de-AT": { // resource for locale `de-AT` (Austrian German) + "translation": { + "greeting": "Servus!" + } + } +} +``` +**Configuration Example:** +```js +const api = new ParseServer({ + ...otherOptions, + pages: { + enableRouter: true, // Enables the experimental feature; required for localization + enableLocalization: true, + localizationJsonPath: './private/localization.json', + localizationFallbackLocale: 'en' + } +} +``` +Pros: +- There is only one HTML file to maintain that contains the placeholders that are filled with the translations according to the locale. +Cons: +- Files cannot be easily previewed by viewing the file in a browser because the content contains only placeholders and even HTML or CSS changes may be dynamically applied, e.g. when a localization requires a Right-To-Left layout direction. +- Style and other fundamental layout changes may be more difficult to apply. +#### Dynamic placeholders +In addition to feature related default parameters such as `appId` and the translations provided via JSON resource, it is possible to define custom dynamic placeholders as part of the router configuration. This works independently of localization and, also if `enableLocalization` is disabled. +**Configuration Example:** +```js +const api = new ParseServer({ + ...otherOptions, + pages: { + enableRouter: true, // Enables the experimental feature; required for localization + placeholders: { + exampleKey: 'exampleValue' + } + } +} +``` +The placeholders can also be provided as function or as async function, with the `locale` and other feature related parameters passed through, to allow for dynamic placeholder values: +```js +const api = new ParseServer({ + ...otherOptions, + pages: { + enableRouter: true, // Enables the experimental feature; required for localization + placeholders: async (params) => { + const value = await doSomething(params.locale); + return { + exampleKey: value + }; + } + } +} +``` +#### Reserved Keys +The following parameter and placeholder keys are reserved because they are used related to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: `appId`, `appName`, `email`, `error`, `locale`, `publicServerUrl`, `token`, `username`. +#### Parameters +| Parameter | Optional | Type | Default value | Example values | Environment variable | Description | +|-------------------------------------------------|----------|---------------------------------------|----------------------------------------|------------------------------------------------------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `pages` | yes | `Object` | `undefined` | - | `PARSE_SERVER_PAGES` | The options for pages such as password reset and email verification. | +| `pages.enableRouter` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_ENABLE_ROUTER` | Is `true` if the pages router should be enabled; this is required for any of the pages options to take effect. **Caution, this is an experimental feature that may not be appropriate for production.** | +| `pages.enableLocalization` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_ENABLE_LOCALIZATION` | Is true if pages should be localized; this has no effect on custom page redirects. | +| `pages.localizationJsonPath` | yes | `String` | `undefined` | `./private/translations.json` | `PARSE_SERVER_PAGES_LOCALIZATION_JSON_PATH` | The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale. | +| `pages.localizationFallbackLocale` | yes | `String` | `en` | `en`, `en-GB`, `default` | `PARSE_SERVER_PAGES_LOCALIZATION_FALLBACK_LOCALE` | The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file. | +| `pages.placeholders` | yes | `Object`, `Function`, `AsyncFunction` | `undefined` | `{ exampleKey: 'exampleValue' }` | `PARSE_SERVER_PAGES_PLACEHOLDERS` | The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function. | +| `pages.forceRedirect` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_FORCE_REDIRECT` | Is `true` if responses should always be redirects and never content, `false` if the response type should depend on the request type (`GET` request -> content response; `POST` request -> redirect response). | +| `pages.pagesPath` | yes | `String` | `./public` | `./files/pages`, `../../pages` | `PARSE_SERVER_PAGES_PAGES_PATH` | The path to the pages directory; this also defines where the static endpoint `/apps` points to. | +| `pages.pagesEndpoint` | yes | `String` | `apps` | - | `PARSE_SERVER_PAGES_PAGES_ENDPOINT` | The API endpoint for the pages. | +| `pages.customUrls` | yes | `Object` | `{}` | `{ passwordReset: 'https://example.com/page.html' }` | `PARSE_SERVER_PAGES_CUSTOM_URLS` | The URLs to the custom pages | +| `pages.customUrls.passwordReset` | yes | `String` | `password_reset.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET` | The URL to the custom page for password reset. | +| `pages.customUrls.passwordResetSuccess` | yes | `String` | `password_reset_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_SUCCESS` | The URL to the custom page for password reset -> success. | +| `pages.customUrls.passwordResetLinkInvalid` | yes | `String` | `password_reset_link_invalid.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_LINK_INVALID` | The URL to the custom page for password reset -> link invalid. | +| `pages.customUrls.emailVerificationSuccess` | yes | `String` | `email_verification_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SUCCESS` | The URL to the custom page for email verification -> success. | +| `pages.customUrls.emailVerificationSendFail` | yes | `String` | `email_verification_send_fail.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_FAIL` | The URL to the custom page for email verification -> link send fail. | +| `pages.customUrls.emailVerificationSendSuccess` | yes | `String` | `email_verification_send_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_SUCCESS` | The URL to the custom page for email verification -> resend link -> success. | +| `pages.customUrls.emailVerificationLinkInvalid` | yes | `String` | `email_verification_link_invalid.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALID` | The URL to the custom page for email verification -> link invalid. | +| `pages.customUrls.emailVerificationLinkExpired` | yes | `String` | `email_verification_link_expired.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIRED` | The URL to the custom page for email verification -> link expired. | +### Notes +- In combination with the [Parse Server API Mail Adapter](https://www.npmjs.com/package/parse-server-api-mail-adapter) Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages. diff --git a/_includes/parse-server/graphql.md b/_includes/parse-server/graphql.md index ab70cc8ae..8649f0352 100644 --- a/_includes/parse-server/graphql.md +++ b/_includes/parse-server/graphql.md @@ -43,279 +43,6 @@ Note: After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. -### Using Express.js - -You can also mount the GraphQL API in an Express.js application together with the REST API or solo. You first need to create a new project and install the required dependencies: - -```bash -$ mkdir my-app -$ cd my-app -$ npm install parse-server express --save -``` - -Then, create an `index.js` file with the following content: - -```js -const express = require('express'); -const { default: ParseServer, ParseGraphQLServer } = require('parse-server'); - -const app = express(); - -const parseServer = new ParseServer({ - databaseURI: 'mongodb://localhost:27017/test', - appId: 'APPLICATION_ID', - masterKey: 'MASTER_KEY', - serverURL: 'http://localhost:1337/parse', - publicServerURL: 'http://localhost:1337/parse' -}); - -const parseGraphQLServer = new ParseGraphQLServer( - parseServer, - { - graphQLPath: '/graphql', - playgroundPath: '/playground' - } -); - -app.use('/parse', parseServer.app); // (Optional) Mounts the REST API -parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API -parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production - -app.listen(1337, function() { - console.log('REST API running on http://localhost:1337/parse'); - console.log('GraphQL API running on http://localhost:1337/graphql'); - console.log('GraphQL Playground running on http://localhost:1337/playground'); -}); -``` - -And finally start your app: - -```bash -$ npx mongodb-runner start -$ node index.js -``` - -After starting the app, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. - -Note: - -- Do **NOT** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps. - -## Checking the API health - -Run the following: - -```graphql -query Health { - health -} -``` - -You should receive the following response: - -```json -{ - "data": { - "health": true - } -} -``` - -## Creating your first class - -Since your application does not have any schema yet, you can use the `createClass` mutation to create your first class. Run the following: - -```graphql -mutation CreateClass { - createClass( - name: "GameScore" - schemaFields: { - addStrings: [{ name: "playerName" }] - addNumbers: [{ name: "score" }] - addBooleans: [{ name: "cheatMode" }] - } - ) { - name - schemaFields { - name - __typename - } - } -} -``` - -You should receive the following response: - -```json -{ - "data": { - "createClass": { - "name": "GameScore", - "schemaFields": [ - { - "name": "objectId", - "__typename": "SchemaStringField" - }, - { - "name": "updatedAt", - "__typename": "SchemaDateField" - }, - { - "name": "createdAt", - "__typename": "SchemaDateField" - }, - { - "name": "playerName", - "__typename": "SchemaStringField" - }, - { - "name": "score", - "__typename": "SchemaNumberField" - }, - { - "name": "cheatMode", - "__typename": "SchemaBooleanField" - }, - { - "name": "ACL", - "__typename": "SchemaACLField" - } - ] - } - } -} -``` - -## Using automatically generated operations - -Parse Server learned from the first class that you created and now you have the `GameScore` class in your schema. You can now start using the automatically generated operations! - -Run the following to create your first object: - -```graphql -mutation CreateGameScore { - createGameScore( - fields: { - playerName: "Sean Plott" - score: 1337 - cheatMode: false - } - ) { - id - updatedAt - createdAt - playerName - score - cheatMode - ACL - } -} -``` - -You should receive a response similar to this: - -```json -{ - "data": { - "createGameScore": { - "id": "XN75D94OBD", - "updatedAt": "2019-09-17T06:50:26.357Z", - "createdAt": "2019-09-17T06:50:26.357Z", - "playerName": "Sean Plott", - "score": 1337, - "cheatMode": false, - "ACL": null - } - } -} -``` - -You can also run a query to this new class: - -```graphql -query GameScores { - gameScores { - results { - id - updatedAt - createdAt - playerName - score - cheatMode - ACL - } - } -} -``` - -You should receive a response similar to this: - -```json -{ - "data": { - "gameScores": { - "results": [ - { - "id": "XN75D94OBD", - "updatedAt": "2019-09-17T06:50:26.357Z", - "createdAt": "2019-09-17T06:50:26.357Z", - "playerName": "Sean Plott", - "score": 1337, - "cheatMode": false, - "ACL": null - } - ] - } - } -} -``` - -## Customizing your GraphQL Schema - -Parse GraphQL Server allows you to create a custom GraphQL schema with own queries and mutations to be merged with the auto-generated ones. You can resolve these operations using your regular cloud code functions. - -To start creating your custom schema, you need to code a `schema.graphql` file and initialize Parse Server with `--graphQLSchema` and `--cloud` options: - -```bash -$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --publicServerURL http://localhost:1337/parse --cloud ./cloud/main.js --graphQLSchema ./cloud/schema.graphql --mountGraphQL --mountPlayground -``` - -### Creating your first custom query - -Use the code below for your `schema.graphql` and `main.js` files. Then restart your Parse Server. - -```graphql -# schema.graphql -extend type Query { - hello: String! @resolve -} -``` - -```js -// main.js -Parse.Cloud.define('hello', async () => { - return 'Hello world!'; -}); -``` - -You can now run your custom query using GraphQL Playground: - -```graphql -query { - hello -} -``` - -You should receive the response below: - -```json -{ - "data": { - "hello": "Hello world!" - } -} -``` - ## Learning more The [Parse GraphQL Guide]({{ site.baseUrl }}/graphql/guide/) is a very good source for learning how to use the Parse GraphQL API. From 93c1902e06d80a29406c5dabdbb58903a069108d Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 11 Feb 2021 12:33:38 +1100 Subject: [PATCH 23/27] Update experimental.md --- _includes/parse-server/experimental.md | 47 ++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index cc6cf70d2..6153631e4 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -54,6 +54,15 @@ Parameters: * `idempotencyOptions.ttl`: The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. + +| Parameter | Optional | Type | Default value | Example values | Environment variable | Description | +|----------------------------|----------|-----------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `idempotencyOptions` | yes | `Object` | `undefined` | | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS | Setting this enables idempotency enforcement for the specified paths. | +| `idempotencyOptions.paths` | yes | `Array` | `[]` | `.*` (all paths, includes the examples below),
`functions/.*` (all functions),
`jobs/.*` (all jobs),
`classes/.*` (all classes),
`functions/.*` (all functions),
`users` (user creation / update),
`installations` (installation creation / update) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHS | An array of path patterns that have to match the request path for request deduplication to be enabled. The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specifiy the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. | +| `idempotencyOptions.ttl` | yes | `Integer` | `300` | `60` (60 seconds) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL | The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. | + + + ### Notes - This feature is currently only available for MongoDB and not for Postgres. @@ -68,26 +77,34 @@ Custom pages as well as feature pages (e.g. password reset, email verification) ```js const api = new ParseServer({ ...otherOptions, + pages: { enableRouter: true, // Enables the experimental feature; required for localization enableLocalization: true, } } ``` + Localization is achieved by matching a request-supplied `locale` parameter with localized page content. The locale can be supplied in either the request query, body or header with the following keys: - query: `locale` - body: `locale` - header: `x-parse-page-param-locale` + For example, a password reset link with the locale parameter in the query could look like this: ``` http://example.com/parse/apps/[appId]/request_password_reset?token=[token]&username=[username]&locale=de-AT ``` + - Localization is only available for pages in the pages directory as set with `pages.pagesPath`. - Localization for feature pages (e.g. password reset, email verification) is disabled if `pages.customUrls` are set, even if the custom URLs point to the pages within the pages path. - Only `.html` files are considered for localization when localizing custom pages. + Pages can be localized in two ways: + #### Localization with Directory Structure + Pages are localized by using the corresponding file in the directory structure where the files are placed in subdirectories named after the locale or language. The file in the base directory is the default file. + **Example Directory Structure:** ```js root/ @@ -98,14 +115,17 @@ root/ │ └── de-AT/ // de-AT locale folder │ │ └── example.html // de-AT localized file ``` + Files are matched with the locale in the following order: 1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`. 1. Language match, e.g. locale `de-CH` matches file in folder `de`. 1. Default; file in base folder is returned. + **Configuration Example:** ```js const api = new ParseServer({ ...otherOptions, + pages: { enableRouter: true, // Enables the experimental feature; required for localization enableLocalization: true, @@ -115,21 +135,28 @@ const api = new ParseServer({ } } ``` + Pros: - All files are complete in their content and can be easily opened and previewed by viewing the file in a browser. + Cons: - In most cases, a localized page differs only slighly from the default page, which could cause a lot of duplicate code that is difficult to maintain. + #### Localization with JSON Resource + Pages are localized by adding placeholders in the HTML files and providing a JSON resource that contains the translations to fill into the placeholders. + **Example Directory Structure:** ```js root/ ├── public/ // pages base path -│ ├── example.html // the page containing placeholders +│ ├── example.html // the page containg placeholders ├── private/ // folder outside of public scope │ └── translations.json // JSON resource file ``` + The JSON resource file loosely follows the [i18next](https://github.com/i18next/i18next) syntax, which is a syntax that is often supported by translation platforms, making it easy to manage translations, exporting them for use in Parse Server, and even to automate this workflow. + **Example JSON Content:** ```json { @@ -150,10 +177,12 @@ The JSON resource file loosely follows the [i18next](https://github.com/i18next/ } } ``` + **Configuration Example:** ```js const api = new ParseServer({ ...otherOptions, + pages: { enableRouter: true, // Enables the experimental feature; required for localization enableLocalization: true, @@ -162,17 +191,23 @@ const api = new ParseServer({ } } ``` + Pros: - There is only one HTML file to maintain that contains the placeholders that are filled with the translations according to the locale. + Cons: - Files cannot be easily previewed by viewing the file in a browser because the content contains only placeholders and even HTML or CSS changes may be dynamically applied, e.g. when a localization requires a Right-To-Left layout direction. - Style and other fundamental layout changes may be more difficult to apply. + #### Dynamic placeholders + In addition to feature related default parameters such as `appId` and the translations provided via JSON resource, it is possible to define custom dynamic placeholders as part of the router configuration. This works independently of localization and, also if `enableLocalization` is disabled. + **Configuration Example:** ```js const api = new ParseServer({ ...otherOptions, + pages: { enableRouter: true, // Enables the experimental feature; required for localization placeholders: { @@ -182,9 +217,11 @@ const api = new ParseServer({ } ``` The placeholders can also be provided as function or as async function, with the `locale` and other feature related parameters passed through, to allow for dynamic placeholder values: + ```js const api = new ParseServer({ ...otherOptions, + pages: { enableRouter: true, // Enables the experimental feature; required for localization placeholders: async (params) => { @@ -196,9 +233,13 @@ const api = new ParseServer({ } } ``` + #### Reserved Keys + The following parameter and placeholder keys are reserved because they are used related to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: `appId`, `appName`, `email`, `error`, `locale`, `publicServerUrl`, `token`, `username`. + #### Parameters + | Parameter | Optional | Type | Default value | Example values | Environment variable | Description | |-------------------------------------------------|----------|---------------------------------------|----------------------------------------|------------------------------------------------------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `pages` | yes | `Object` | `undefined` | - | `PARSE_SERVER_PAGES` | The options for pages such as password reset and email verification. | @@ -219,5 +260,7 @@ The following parameter and placeholder keys are reserved because they are used | `pages.customUrls.emailVerificationSendSuccess` | yes | `String` | `email_verification_send_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_SUCCESS` | The URL to the custom page for email verification -> resend link -> success. | | `pages.customUrls.emailVerificationLinkInvalid` | yes | `String` | `email_verification_link_invalid.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALID` | The URL to the custom page for email verification -> link invalid. | | `pages.customUrls.emailVerificationLinkExpired` | yes | `String` | `email_verification_link_expired.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIRED` | The URL to the custom page for email verification -> link expired. | + ### Notes -- In combination with the [Parse Server API Mail Adapter](https://www.npmjs.com/package/parse-server-api-mail-adapter) Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages. + +- In combination with the [Parse Server API Mail Adapter](https://www.npmjs.com/package/parse-server-api-mail-adapter) Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages. \ No newline at end of file From b4cfa749df71d93b329ceeb591a4dd192aff197e Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 12 Feb 2021 00:36:48 +1100 Subject: [PATCH 24/27] Update experimental.md --- _includes/parse-server/experimental.md | 186 ++++++++++++++++++++----- 1 file changed, 155 insertions(+), 31 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 6153631e4..a93f45efb 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -54,15 +54,6 @@ Parameters: * `idempotencyOptions.ttl`: The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. - -| Parameter | Optional | Type | Default value | Example values | Environment variable | Description | -|----------------------------|----------|-----------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `idempotencyOptions` | yes | `Object` | `undefined` | | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS | Setting this enables idempotency enforcement for the specified paths. | -| `idempotencyOptions.paths` | yes | `Array` | `[]` | `.*` (all paths, includes the examples below),
`functions/.*` (all functions),
`jobs/.*` (all jobs),
`classes/.*` (all classes),
`functions/.*` (all functions),
`users` (user creation / update),
`installations` (installation creation / update) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHS | An array of path patterns that have to match the request path for request deduplication to be enabled. The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specifiy the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. | -| `idempotencyOptions.ttl` | yes | `Integer` | `300` | `60` (60 seconds) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL | The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. | - - - ### Notes - This feature is currently only available for MongoDB and not for Postgres. @@ -238,28 +229,161 @@ const api = new ParseServer({ The following parameter and placeholder keys are reserved because they are used related to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: `appId`, `appName`, `email`, `error`, `locale`, `publicServerUrl`, `token`, `username`. -#### Parameters - -| Parameter | Optional | Type | Default value | Example values | Environment variable | Description | -|-------------------------------------------------|----------|---------------------------------------|----------------------------------------|------------------------------------------------------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `pages` | yes | `Object` | `undefined` | - | `PARSE_SERVER_PAGES` | The options for pages such as password reset and email verification. | -| `pages.enableRouter` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_ENABLE_ROUTER` | Is `true` if the pages router should be enabled; this is required for any of the pages options to take effect. **Caution, this is an experimental feature that may not be appropriate for production.** | -| `pages.enableLocalization` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_ENABLE_LOCALIZATION` | Is true if pages should be localized; this has no effect on custom page redirects. | -| `pages.localizationJsonPath` | yes | `String` | `undefined` | `./private/translations.json` | `PARSE_SERVER_PAGES_LOCALIZATION_JSON_PATH` | The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale. | -| `pages.localizationFallbackLocale` | yes | `String` | `en` | `en`, `en-GB`, `default` | `PARSE_SERVER_PAGES_LOCALIZATION_FALLBACK_LOCALE` | The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file. | -| `pages.placeholders` | yes | `Object`, `Function`, `AsyncFunction` | `undefined` | `{ exampleKey: 'exampleValue' }` | `PARSE_SERVER_PAGES_PLACEHOLDERS` | The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function. | -| `pages.forceRedirect` | yes | `Boolean` | `false` | - | `PARSE_SERVER_PAGES_FORCE_REDIRECT` | Is `true` if responses should always be redirects and never content, `false` if the response type should depend on the request type (`GET` request -> content response; `POST` request -> redirect response). | -| `pages.pagesPath` | yes | `String` | `./public` | `./files/pages`, `../../pages` | `PARSE_SERVER_PAGES_PAGES_PATH` | The path to the pages directory; this also defines where the static endpoint `/apps` points to. | -| `pages.pagesEndpoint` | yes | `String` | `apps` | - | `PARSE_SERVER_PAGES_PAGES_ENDPOINT` | The API endpoint for the pages. | -| `pages.customUrls` | yes | `Object` | `{}` | `{ passwordReset: 'https://example.com/page.html' }` | `PARSE_SERVER_PAGES_CUSTOM_URLS` | The URLs to the custom pages | -| `pages.customUrls.passwordReset` | yes | `String` | `password_reset.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET` | The URL to the custom page for password reset. | -| `pages.customUrls.passwordResetSuccess` | yes | `String` | `password_reset_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_SUCCESS` | The URL to the custom page for password reset -> success. | -| `pages.customUrls.passwordResetLinkInvalid` | yes | `String` | `password_reset_link_invalid.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_LINK_INVALID` | The URL to the custom page for password reset -> link invalid. | -| `pages.customUrls.emailVerificationSuccess` | yes | `String` | `email_verification_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SUCCESS` | The URL to the custom page for email verification -> success. | -| `pages.customUrls.emailVerificationSendFail` | yes | `String` | `email_verification_send_fail.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_FAIL` | The URL to the custom page for email verification -> link send fail. | -| `pages.customUrls.emailVerificationSendSuccess` | yes | `String` | `email_verification_send_success.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_SUCCESS` | The URL to the custom page for email verification -> resend link -> success. | -| `pages.customUrls.emailVerificationLinkInvalid` | yes | `String` | `email_verification_link_invalid.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALID` | The URL to the custom page for email verification -> link invalid. | -| `pages.customUrls.emailVerificationLinkExpired` | yes | `String` | `email_verification_link_expired.html` | - | `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIRED` | The URL to the custom page for email verification -> link expired. | +### Parameters + +**pages** + +_Optional `Object`. Default: `undefined`._ + +The options for pages such as password reset and email verification. + +Environment variable: `PARSE_SERVER_PAGES` + +**pages.enableRouter** + +_Optional `Boolean`. Default: `false`._ + +Is `true` if the pages router should be enabled; this is required for any of the pages options to take effect. + +Environment variable: `PARSE_SERVER_PAGES_ENABLE_ROUTER` + +**pages.enableLocalization** + +_Optional `Boolean`. Default: `false`._ + +Is `true` if pages should be localized; this has no effect on custom page redirects. + +Environment variable: `PARSE_SERVER_PAGES_ENABLE_LOCALIZATION` + +**pages.localizationJsonPath** + +_Optional `String`. Default: `undefined`._ + +The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale. + +Example: `./private/translations.json` + +Environment variable: `PARSE_SERVER_PAGES_LOCALIZATION_JSON_PATH` + +**pages.localizationFallbackLocale** + +_Optional `String`. Default: `en`._ + +The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file. + +Example: `en`, `en-GB`, `default` + +Environment variable: `PARSE_SERVER_PAGES_LOCALIZATION_FALLBACK_LOCALE` + +**pages.placeholders** + +_Optional `Object`, `Function`, or `AsyncFunction`. Default: `undefined`._ + +The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function. + +Example: `{ exampleKey: 'exampleValue' }` + +Environment variable: `PARSE_SERVER_PAGES_PLACEHOLDERS` + +**pages.forceRedirect** + +_Optional `Boolean`. Default: `false`._ + +Is `true` if responses should always be redirects and never content, false if the response type should depend on the request type (`GET` request -> content response; `POST` request -> redirect response). + +Environment variable: `PARSE_SERVER_PAGES_FORCE_REDIRECT` + +**pages.pagesPath** + +_Optional `String`. Default: `./public`._ + +The path to the pages directory; this also defines where the static endpoint `/apps` points to. + +Example: `./files/pages`, `../../pages` + +Environment variable: `PARSE_SERVER_PAGES_PAGES_PATH` + +**pages.pagesEndpoint** + +_Optional `String`. Default: `apps`._ + +The API endpoint for the pages. + +Environment variable: `PARSE_SERVER_PAGES_PAGES_ENDPOINT` + +**pages.customUrls** + +_Optional `Object`. Default: `{}`._ + +The URLs to the custom pages. + +Example: `{ passwordReset: 'https://example.com/page.html' }` + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URLS` + +**pages.customUrls.passwordReset** + +_Optional `String`. Default: `password_reset.html`._ + +The URL to the custom page for password reset. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET` + +**pages.customUrls.passwordResetSuccess** + +_Optional `String`. Default: `password_reset_success.html`._ + +The URL to the custom page for password reset -> success. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_SUCCESS` + +**pages.customUrls.passwordResetLinkInvalid** + +_Optional `String`. Default: `password_reset_link_invalid.html`._ + +The URL to the custom page for password reset -> link invalid. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_LINK_INVALID` + +**pages.customUrls.emailVerificationSuccess** + +_Optional `String`. Default: `email_verification_success.html`._ + +The URL to the custom page for email verification -> success. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SUCCESS` + +**pages.customUrls.emailVerificationSendFail** + +_Optional `String`. Default: `email_verification_send_fail.html`._ + +The URL to the custom page for email verification -> link send fail. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_FAIL` + +**pages.customUrls.emailVerificationSendSuccess** + +_Optional `String`. Default: `email_verification_send_success.html`._ + +The URL to the custom page for email verification -> resend link -> success. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_SUCCESS` + +**pages.customUrls.emailVerificationLinkInvalid** + +_Optional `String`. Default: `email_verification_link_invalid.html`._ + +The URL to the custom page for email verification -> link invalid. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALID` + +**pages.customUrls.emailVerificationLinkExpired** + +_Optional `String`. Default: `email_verification_link_expired.html`._ + +The URL to the custom page for email verification -> link expired. + +Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIRED` ### Notes From 859b535b5a1ac8469d80943e99b38f01b4ff5adb Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 26 May 2021 11:35:31 +0100 Subject: [PATCH 25/27] Apply suggestions from code review --- _includes/parse-server/experimental.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index a93f45efb..0cdd8e8f5 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -61,7 +61,6 @@ Parameters: ## Localization ### Pages -**Caution, this is an experimental feature that may not be appropriate for production.** Custom pages as well as feature pages (e.g. password reset, email verification) can be localized with the `pages` option in the Parse Server configuration: @@ -109,8 +108,8 @@ root/ Files are matched with the locale in the following order: 1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`. -1. Language match, e.g. locale `de-CH` matches file in folder `de`. -1. Default; file in base folder is returned. +2. Language match, e.g. locale `de-CH` matches file in folder `de`. +3. Default; file in base folder is returned. **Configuration Example:** ```js @@ -184,7 +183,7 @@ const api = new ParseServer({ ``` Pros: -- There is only one HTML file to maintain that contains the placeholders that are filled with the translations according to the locale. +- There is only one HTML file to maintain containing the placeholders which are filled with the translations according to the locale. Cons: - Files cannot be easily previewed by viewing the file in a browser because the content contains only placeholders and even HTML or CSS changes may be dynamically applied, e.g. when a localization requires a Right-To-Left layout direction. @@ -207,7 +206,7 @@ const api = new ParseServer({ } } ``` -The placeholders can also be provided as function or as async function, with the `locale` and other feature related parameters passed through, to allow for dynamic placeholder values: +The placeholders can also be provided as a function or as an async function, with the `locale` and other feature related parameters passed through, to allow for dynamic placeholder values: ```js const api = new ParseServer({ @@ -227,7 +226,7 @@ const api = new ParseServer({ #### Reserved Keys -The following parameter and placeholder keys are reserved because they are used related to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: `appId`, `appName`, `email`, `error`, `locale`, `publicServerUrl`, `token`, `username`. +The following parameter and placeholder keys are reserved because they are used in relation to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: `appId`, `appName`, `email`, `error`, `locale`, `publicServerUrl`, `token`, `username`. ### Parameters @@ -387,4 +386,4 @@ Environment variable: `PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXP ### Notes -- In combination with the [Parse Server API Mail Adapter](https://www.npmjs.com/package/parse-server-api-mail-adapter) Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages. \ No newline at end of file +- In combination with the [Parse Server API Mail Adapter](https://www.npmjs.com/package/parse-server-api-mail-adapter) Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages. From ed55f5dbbb9238ff6a0d32e8b36864e10063cfe5 Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 26 May 2021 11:43:37 +0100 Subject: [PATCH 26/27] improve experimental note --- _includes/parse-server/experimental.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 0cdd8e8f5..2ebfecf20 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -1,8 +1,8 @@ # Experimental -Experimental features may not be 'battle tested' and could have issues. They are likely to become full features at some point, but could be removed from the system if they are problematic. +The following features are still under active development and subject to an increased probability of bugs, breaking changes, non-backward compatible changes or removal in any future version. It's not recommended to use these features in production or other critical environments. -These features may not be approprate for production, so use at your own risk. +However, we strongly appreciate if you try out these features in development environments and provide feedback, so that they can mature faster and become production fit. ## Direct Access From 9ba83e955ad997f0eb86d1d5037f942f1e56cda2 Mon Sep 17 00:00:00 2001 From: Tom Fox <13188249+TomWFox@users.noreply.github.com> Date: Wed, 26 May 2021 11:49:21 +0100 Subject: [PATCH 27/27] improve direct access explanation --- _includes/parse-server/experimental.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/_includes/parse-server/experimental.md b/_includes/parse-server/experimental.md index 2ebfecf20..f3f2a7437 100644 --- a/_includes/parse-server/experimental.md +++ b/_includes/parse-server/experimental.md @@ -6,7 +6,11 @@ However, we strongly appreciate if you try out these features in development env ## Direct Access -`directAccess` replaces the HTTP Interface when using the JS SDK in the current node runtime. This may improve performance, along with `enableSingleSchemaCache` set to `true`. +A Parse Server request in Cloud Code (such as a query) is usually sent to Parse Server via the HTTP interface, just as if the request came from outside Cloud Code and outside the current Node.js instance. This is true for all requests made with the Parse JavaScript SDK within the Node.js runtime in which Parse Server is running. + +Setting `directAccess: true` instead processes requests to Parse Server directly within the Parse instance without using the HTTP interface. This may improve performance, along with `enableSingleSchemaCache` set to `true`. + +Direct Access also has the side effect that requests within Cloud Code cannot be distributed via a load balancer when Parse Server is running in a multi-instance environment. Configuration: ```js