diff --git a/.travis.yml b/.travis.yml index 44e903339d..f5383e20e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ branches: - master - "/^[0-9]+.[0-9]+.[0-9]+(-.*)?$/" - 3.x + - 4.x - "/^greenkeeper/.*$/" cache: directories: @@ -54,7 +55,7 @@ after_script: jobs: allow_failures: - env: NODE_VERSION=12.12.0 - include: + include: - stage: release node_js: '10' env: @@ -64,18 +65,18 @@ jobs: - "./release_docs.sh" deploy: - provider: pages - cleanup: true + skip_cleanup: true token: secure: YU2lUqmW036AHBRu7xO/AwEeQ900Q/5O6FL96ZqWEfD7Gadaq4iapkNvhPR0HcZYRHqQV2/2LCHVnhd0dbj0ShkmVIHdQE7O+MF+j0v0GIVc8FPTPe1/d2Hy4apSWNe6FeCrYKVeliAu+ZqvNuFLhVmYvIeJdlJrMGOb6P76UZXRDv2srXhq4uBcCVUJuTajyyd5ttJfcNapymTP+xzEDYc7Hr4LJaubmv/wVD/xwPBbvfYFuBqysUpkPKi/ODlQbB0ybYh2fFnX71WUyFUGbtB5xI9ma951Zp4v3t73c31uUl27dJaHzO62EtVTdcUKAa804EtAtsvpeJWMVWKUgigm9UZcXdEwKa79fl5nLaq34lrSktAYOkexwPqYj+vbS6sn52JrluSxLE+4cEke7tYbnJ9X12SAQXKgcXY3n30+6gKf9RVqYdlNsYpEAqKVIDb6SlEkk86dP+uIg/XXb5RKEwxbDXnb6xdl9JRc+GTbeeY3/vg2h9QTdmFVblPtBhHrNenQYP/BS0n+EfUnAIBKqRmQgyhao3SiY5FMACM8higI2Lvvhpq46pDhXqsexYCz5F008C6YXGDh5gC93rJFec0pjh55DNdQu6uw3YMQ5jtf3QUXoPAoMFud3cTulMlnjC1WZG8QbqER8dzUZ4TcaQUdzribJI/mRriheBg= local_dir: docs/ on: all_branches: true - provider: npm + skip_cleanup: true api_token: secure: Yrng6jsnMAtzrrln9DwRuY4xpcxl/WYS/1A5fckyQF6DsLmNlvqVtu3MWF+zdJgANF63G3jIee11tNBpYRecHl8FjPGwO0kc1vEgvIRVnveyR0bwIIDH6s/mR9dg4rZikflwG4XExsLyaQd7ko8aTIOIayfxJiv/u0yqwuBuBW2bFrL/41b1cKGK5+Iq2a+PdFENUPenKXISkACGaMnQF4Y/KVF98UwCiGLf957yFWc2sD6TFbjNDbAENSccvg1J3fBb+djbtzKzldl29ntp2mKVGSVASiKCRa6hSfgQulHiidFqFIKgpYJ1uATRr3UFr/NRVt1WbrgLnzY74OCX7y02c/xiYQMMEPRl/P8hHJu+KjQ3PBWsBvmsRN0QMUJv3PEjUPE3AY8sw49NoxiJZzJr7574vUBuM/dk0byYI6K/gwmUrPIhQjljzZqinS8JJJ4FjGjCdzXRhT5Q+PZvt5bF1rMOZXayNJZa+cICtmiJoU3vO2Yf6TXC3wY1veKvmcs24nws5lp4keJePTKAi1Ig7VoSxwAlgs3EGANIh7oBKx9zWnXQYMdmxbYsvLXAWcnnM+PcSCvooLmXWso3BQBeRuUUSS2oLaBpsFMsiniLNbA1cW4fwMkgUGz4oEDRi8wTD89E/1J2oNxzqtWRPawcVKpMpnBbDJHrWJPEHMw= email: secure: lomzDl71N995SzRczm7VE9OZE+PzMo4X8t3zU97agu/FMH5Qcj8BLwE+uVDTnA9Vblyj62ZsFKsNjP2Qp53Vcd+jHM4EJNWNRZYpEMIRO3LngX43r83qoFEHUvPu9s1oaa04a6FojcsJx0wl6B6Ke2AX74MXnJDLb9iZBy1mkpLUMVccVhSfhdoIzhkq3dhUw+6d8C024tNMHcgDW3VnRAsWFtiL7dCMpjLOdI+UxlkeGkQkxXXuRsZ0ZdjoSoM8NSkiYMc8x6EnekyRDoHTujX3OFxuU6+GAjrUmVzNmJWrBIqHVb0DXBQxjEaG3d/cNu5UsQyZYq5sxRRH0BaLs7F4oIQg95etasEtTtUkmsZ3pshVlsweiLU366UdbfuAf5hrJjqLrU12BKZyLjaAwyeKz031r8dA4sJtGIp5uVdXobQQTH6r958A88byJ20uaYSqhqjhZo3hkWXIQP0WQN2Ej/g57HbVNLB/nPKkMILfk/tpp7nBDLT0QrjbZxeo1dwCHqsBEV6z7ZyWyFf4xwpDsir4txL4t8ElzeGdlACjCqAJvIh5w9YzfrwijtoVMvvP6pWvn/iI640d4rsdIDe8egxgqZ1R/TMd/tdHYX+eI+ZfFmCVGj36/uXwdG7KIoIZVjRQ2tvWr9ZuydEPWPSRVGT4ycFeu6wbm3elM3I= - cleanup: true on: tags: true all_branches: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 1488539480..157b2bd0b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,44 @@ ## Parse Server Changelog ### master - -[Full Changelog](https://github.com/parse-community/parse-server/compare/3.10.0...master) -- FIX: FIX: Prevent new usernames or emails that clash with existing users' email or username if it only differs by case. For example, don't allow a new user with the name 'Jane' if we already have a user 'jane'. [#5634](https://github.com/parse-community/parse-server/pull/5634). Thanks to [Arthur Cinader](https://github.com/acinader) +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.1.0...master) + +### 4.1.0 +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.0.2...4.1.0) +_SECURITY RELEASE_: see [advisory](https://github.com/parse-community/parse-server/security/advisories/GHSA-h4mf-75hf-67w4) for details +- SECURITY FIX: Patch Regex vulnerabilities. See [3a3a5ee](https://github.com/parse-community/parse-server/commit/3a3a5eee5ffa48da1352423312cb767de14de269). Special thanks to [W0lfw00d](https://github.com/W0lfw00d) for identifying and [responsibly reporting](https://github.com/parse-community/parse-server/blob/master/SECURITY.md) the vulnerability. Thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo) for the speedy fix. + +### 4.0.2 +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.0.1...4.0.2) +__BREAKING CHANGES:__ +1. Remove Support for Mongo 3.2 & 3.4. The new minimum supported version is Mongo 3.6. +2. Change username and email validation to be case insensitive. This change should be transparent in most use cases. The validation behavior should now behave 'as expected'. See [#6414](https://github.com/parse-community/parse-server/pull/6414) for details. + +FIX: attempt to get travis to deploy to npmjs again. See [#6475](https://github.com/parse-community/parse-server/pull/6457). Thanks to [Arthur Cinader](https://github.com/acinader). + +### 4.0.1 +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.0.0...4.0.1) +- FIX: correct 'new' travis config to properly deploy. See [#6452](https://github.com/parse-community/parse-server/pull/6452). Thanks to [Arthur Cinader](https://github.com/acinader). +- FIX: Better message on not allowed to protect default fields. See [#6439](https://github.com/parse-community/parse-server/pull/6439).Thanks to [Old Grandpa](https://github.com/BufferUnderflower) + +### 4.0 +[Full Changelog](https://github.com/parse-community/parse-server/compare/3.10.0...4.0.0) +- NEW: add hint option to Parse.Query [#6322](https://github.com/parse-community/parse-server/pull/6322). Thanks to [Steve Stencil](https://github.com/stevestencil) +- FIX: CLP objectId size validation fix [#6332](https://github.com/parse-community/parse-server/pull/6332). Thanks to [Old Grandpa](https://github.com/BufferUnderflower) +- FIX: Add volumes to Docker command [#6356](https://github.com/parse-community/parse-server/pull/6356). Thanks to [Kasra Bigdeli](https://github.com/githubsaturn) +- NEW: GraphQL 3rd Party LoginWith Support [#6371](https://github.com/parse-community/parse-server/pull/6371). Thanks to [Antoine Cormouls](https://github.com/Moumouls) +- FIX: GraphQL Geo Queries [#6363](https://github.com/parse-community/parse-server/pull/6363). Thanks to [Antoine Cormouls](https://github.com/Moumouls) +- NEW: GraphQL Nested File Upload [#6372](https://github.com/parse-community/parse-server/pull/6372). Thanks to [Antoine Cormouls](https://github.com/Moumouls) +- NEW: Granular CLP pointer permissions [#6352](https://github.com/parse-community/parse-server/pull/6352). Thanks to [Old Grandpa](https://github.com/BufferUnderflower) +- FIX: Add missing colon for customPages [#6393](https://github.com/parse-community/parse-server/pull/6393). Thanks to [Jerome De Leon](https://github.com/JeromeDeLeon) +NEW: `afterLogin` cloud code hook [#6387](https://github.com/parse-community/parse-server/pull/6387). Thanks to [David Corona](https://github.com/davesters) +- FIX: __BREAKING CHANGE__ Prevent new usernames or emails that clash with existing users' email or username if it only differs by case. For example, don't allow a new user with the name 'Jane' if we already have a user 'jane'. [#5634](https://github.com/parse-community/parse-server/pull/5634). Thanks to [Arthur Cinader](https://github.com/acinader) +- FIX: Support Travis CI V2. [#6414](https://github.com/parse-community/parse-server/pull/6414). Thanks to [Diamond Lewis](https://github.com/dplewis) +- FIX: Prevent crashing on websocket error. [#6418](https://github.com/parse-community/parse-server/pull/6418). Thanks to [Diamond Lewis](https://github.com/dplewis) +- NEW: Allow protectedFields for Authenticated users and Public. [$6415](https://github.com/parse-community/parse-server/pull/6415). Thanks to [Old Grandpa](https://github.com/BufferUnderflower) +- FIX: Correct bug in determining GraphQL pointer errors when mutating. [#6413](https://github.com/parse-community/parse-server/pull/6431). Thanks to [Antoine Cormouls](https://github.com/Moumouls) +- NEW: Allow true GraphQL Schema Customization. [#6360](https://github.com/parse-community/parse-server/pull/6360). Thanks to [Antoine Cormouls](https://github.com/Moumouls) +- __BREAKING CHANGE__: Remove Support for Mongo version < 3.6 [#6445](https://github.com/parse-community/parse-server/pull/6445). Thanks to [Arthur Cinader](https://github.com/acinader) ### 3.10.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/3.9.0...3.10.0) @@ -21,8 +56,8 @@ - NEW: GraphQL: DX Relational Where Query [#6255](https://github.com/parse-community/parse-server/pull/6255). Thanks to [Antoine Cormouls](https://github.com/Moumouls) - CHANGE: test against Postgres 11 [#6260](https://github.com/parse-community/parse-server/pull/6260). Thanks to [Diamond Lewis](https://github.com/dplewis) - CHANGE: test against Postgres 11 [#6260](https://github.com/parse-community/parse-server/pull/6260). Thanks to [Diamond Lewis](https://github.com/dplewis) -- NEW: GraphQL alias for mutations in classConfigs [#6258](https://github.com/parse-community/parse-server/pull/6258). Thanks to [Old Grandpa ](https://github.com/BufferUnderflower) -- NEW: GraphQL classConfig query alias [#6257](https://github.com/parse-community/parse-server/pull/6257). Thanks to [Old Grandpa ](https://github.com/BufferUnderflower) +- NEW: GraphQL alias for mutations in classConfigs [#6258](https://github.com/parse-community/parse-server/pull/6258). Thanks to [Old Grandpa](https://github.com/BufferUnderflower) +- NEW: GraphQL classConfig query alias [#6257](https://github.com/parse-community/parse-server/pull/6257). Thanks to [Old Grandpa](https://github.com/BufferUnderflower) - NEW: Allow validateFilename to return a string or Parse Error [#6246](https://github.com/parse-community/parse-server/pull/6246). Thanks to [Mike Patnode](https://github.com/mpatnode) - NEW: Relay Spec [#6089](https://github.com/parse-community/parse-server/pull/6089). Thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo) - CHANGE: Set default ACL for GraphQL [#6249](https://github.com/parse-community/parse-server/pull/6249). Thanks to [Antoine Cormouls](https://github.com/Moumouls) diff --git a/README.md b/README.md index e73f9b9044..b935b83e03 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@

- MongoDB 3.2 - MongoDB 3.4 MongoDB 3.6 MongoDB 4.0

@@ -80,7 +78,7 @@ The fastest and easiest way to get started is to run MongoDB and Parse Server lo Before you start make sure you have installed: -- [NodeJS](https://www.npmjs.com/) that includes `npm` +- [NodeJS](https://www.npmjs.com/) that includes `npm` - [MongoDB](https://www.mongodb.com/) or [PostgreSQL](https://www.postgresql.org/) - Optionally [Docker](https://www.docker.com/) @@ -337,7 +335,7 @@ It’s possible to change the default pages of the app and redirect the user to ```js var server = ParseServer({ ...otherOptions, - + customPages: { passwordResetSuccess: "http://yourapp.com/passwordResetSuccess", verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess", diff --git a/package-lock.json b/package-lock.json index 957b5f97da..36d463bbb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "3.10.0", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -25,9 +25,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.15.tgz", - "integrity": "sha512-daFGV9GSs6USfPgxceDA8nlSe48XrVCJfDeYm7eokxq/ye7iuOH87hKXgMtEAVLFapkczbZsx868PMDT1Y0a6A==" + "version": "10.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", + "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==" } } }, @@ -79,9 +79,9 @@ } }, "@babel/compat-data": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.4.tgz", - "integrity": "sha512-lHLhlsvFjJAqNU71b7k6Vv9ewjmTXKvqaMv7n0G1etdCabWLw3nEYE8mmgoVOxMIFE07xOvo7H7XBASirX6Rrg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.6.tgz", + "integrity": "sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==", "dev": true, "requires": { "browserslist": "^4.8.5", @@ -98,18 +98,18 @@ } }, "@babel/core": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", - "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.6.tgz", + "integrity": "sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helpers": "^7.8.4", - "@babel/parser": "^7.8.4", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -130,12 +130,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -182,43 +182,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -279,9 +279,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -302,9 +302,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -335,12 +335,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -387,43 +387,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -449,12 +449,12 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", - "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.6.tgz", + "integrity": "sha512-UrJdk27hKVJSnibFcUWYLkCL0ZywTUoot8yii1lsHJcvwrypagmYKjHLMWivQPm4s6GdyygCL8fiH5EYLxhQwQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.4", + "@babel/compat-data": "^7.8.6", "browserslist": "^4.8.5", "invariant": "^2.2.4", "levenary": "^1.1.1", @@ -470,11 +470,12 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", - "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz", + "integrity": "sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A==", "dev": true, "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", "@babel/helper-regex": "^7.8.3", "regexpu-core": "^4.6.0" } @@ -531,26 +532,26 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -580,12 +581,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -632,43 +633,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -723,9 +724,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -745,9 +746,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -767,9 +768,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -780,16 +781,17 @@ } }, "@babel/helper-module-transforms": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", - "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz", + "integrity": "sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", "@babel/helper-simple-access": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.8.6", "lodash": "^4.17.13" }, "dependencies": { @@ -823,26 +825,26 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -862,9 +864,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -912,12 +914,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -964,43 +966,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1026,15 +1028,15 @@ } }, "@babel/helper-replace-supers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", - "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" }, "dependencies": { "@babel/code-frame": { @@ -1047,12 +1049,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1099,43 +1101,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1191,26 +1193,26 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1251,12 +1253,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1303,43 +1305,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1385,12 +1387,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1437,43 +1439,43 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1717,9 +1719,9 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", - "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz", + "integrity": "sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.8.3", @@ -1727,7 +1729,7 @@ "@babel/helper-function-name": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", "@babel/helper-split-export-declaration": "^7.8.3", "globals": "^11.1.0" }, @@ -1782,26 +1784,26 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1869,9 +1871,9 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", - "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz", + "integrity": "sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -1928,26 +1930,26 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2069,9 +2071,9 @@ } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2166,13 +2168,13 @@ } }, "@babel/preset-env": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz", - "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.6.tgz", + "integrity": "sha512-M5u8llV9DIVXBFB/ArIpqJuvXpO+ymxcJ6e8ZAmzeK3sQeBNOD1y+rHvHCGG4TlEmsNpIrdecsHGHT8ZCoOSJg==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.4", - "@babel/helper-compilation-targets": "^7.8.4", + "@babel/compat-data": "^7.8.6", + "@babel/helper-compilation-targets": "^7.8.6", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -2195,13 +2197,13 @@ "@babel/plugin-transform-async-to-generator": "^7.8.3", "@babel/plugin-transform-block-scoped-functions": "^7.8.3", "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.8.3", + "@babel/plugin-transform-classes": "^7.8.6", "@babel/plugin-transform-computed-properties": "^7.8.3", "@babel/plugin-transform-destructuring": "^7.8.3", "@babel/plugin-transform-dotall-regex": "^7.8.3", "@babel/plugin-transform-duplicate-keys": "^7.8.3", "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.4", + "@babel/plugin-transform-for-of": "^7.8.6", "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", @@ -2222,7 +2224,7 @@ "@babel/plugin-transform-template-literals": "^7.8.3", "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "browserslist": "^4.8.5", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -2231,9 +2233,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2536,9 +2538,9 @@ } }, "@types/body-parser": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", - "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", "requires": { "@types/connect": "*", "@types/node": "*" @@ -2626,9 +2628,9 @@ "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "@types/koa": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.1.tgz", - "integrity": "sha512-/kqQs+8Qd9GL0cdl39HEhK91k7xq6+Zci76RUdqtTLj1Mg1aVG7zwJm3snkeyFUeAvY8noY27eMXgqg1wHZgwA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.2.tgz", + "integrity": "sha512-2UPelagNNW6bnc1I5kIzluCaheXRA9S+NyOdXEFFj9Az7jc15ek5V03kb8OTbb3tdZ5i2BIJObe86PhHvpMolg==", "requires": { "@types/accepts": "*", "@types/cookies": "*", @@ -2879,12 +2881,12 @@ } }, "apollo-cache-control": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.8.11.tgz", - "integrity": "sha512-8yz4qbRBIFDWRHdT8uPh0HHh+VbQXxoFGJQRAG8hyMRvR+EuURXX1ltXYkn5J3YJ3MKEqgsvwGaq60dFZq63UQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.9.0.tgz", + "integrity": "sha512-iLT6IT4Ul5cMfBcJAvhpk3a7AD6fXqvFxNmJEPVapVJHbSKYIjra4PTis13sOyN5Y3WQS6a+NRFxAW8+hL3q3Q==", "requires": { "apollo-server-env": "^2.4.3", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-cache-inmemory": { @@ -2926,18 +2928,18 @@ } }, "apollo-engine-reporting": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.6.0.tgz", - "integrity": "sha512-prA17Tp/WYBJdCd4ey1CnGX8d4Xis1n9PsFmT7x8PV/oNpxG21/x3yNw5kPBZuKAoKz8yEggYtHhkYie1ZBjPQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.7.0.tgz", + "integrity": "sha512-jsjSnoHrRmk4XXK4aFU17YSJILmWsilKRwIeN74QJsSxjn5SCVF4EI/ebf/MNrTHpft8EhShx+wdkAcOD9ivqA==", "requires": { "apollo-engine-reporting-protobuf": "^0.4.4", "apollo-graphql": "^0.4.0", "apollo-server-caching": "^0.5.1", "apollo-server-env": "^2.4.3", - "apollo-server-errors": "^2.3.4", - "apollo-server-types": "^0.2.10", + "apollo-server-errors": "^2.4.0", + "apollo-server-types": "^0.3.0", "async-retry": "^1.2.1", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-engine-reporting-protobuf": { @@ -3020,25 +3022,25 @@ } }, "apollo-server-core": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.10.1.tgz", - "integrity": "sha512-BVITSJRMnj+CWFkjt7FMcaoqg/Ni9gfyVE9iu8bUc1IebBfFDcQj652Iolr7dTqyUziN2jbf0wfcybKYJLQHQQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.11.0.tgz", + "integrity": "sha512-jHLOqwTRlyWzqWNRlwr2M/xfrt+lw2pHtKYyxUGRjWFo8EM5TX1gDcTKtbtvx9p5m+ZBDAhcWp/rpq0vSz4tqg==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "@apollographql/graphql-playground-html": "1.6.24", "@types/graphql-upload": "^8.0.0", "@types/ws": "^6.0.0", - "apollo-cache-control": "^0.8.11", + "apollo-cache-control": "^0.9.0", "apollo-datasource": "^0.7.0", - "apollo-engine-reporting": "^1.6.0", + "apollo-engine-reporting": "^1.7.0", "apollo-server-caching": "^0.5.1", "apollo-server-env": "^2.4.3", - "apollo-server-errors": "^2.3.4", - "apollo-server-plugin-base": "^0.6.10", - "apollo-server-types": "^0.2.10", - "apollo-tracing": "^0.8.11", + "apollo-server-errors": "^2.4.0", + "apollo-server-plugin-base": "^0.7.0", + "apollo-server-types": "^0.3.0", + "apollo-tracing": "^0.9.0", "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "^0.10.10", + "graphql-extensions": "^0.11.0", "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", @@ -3083,23 +3085,23 @@ } }, "apollo-server-errors": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz", - "integrity": "sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.0.tgz", + "integrity": "sha512-ZouZfr2sGavvI18rgdRcyY2ausRAlVtWNOax9zca8ZG2io86dM59jXBmUVSNlVZSmBsIh45YxYC0eRvr2vmRdg==" }, "apollo-server-express": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.10.1.tgz", - "integrity": "sha512-NkuWGBOCTiju/aDjfvDImm+4yzfrM0dwiRxu9fKwwh2h1oYBUKJNqjQ1mzJRi0ks6Sn1egwl/fQkTBTkWwGx7Q==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.11.0.tgz", + "integrity": "sha512-9bbiD+zFAx+xyurc9lxYmNa9y79k/gsA1vEyPFVcv7jxzCFC5wc0tcbV7NPX2qi1Nn7K76fxo2fPNYbPFX/y0g==", "requires": { "@apollographql/graphql-playground-html": "1.6.24", "@types/accepts": "^1.3.5", - "@types/body-parser": "1.17.1", + "@types/body-parser": "1.19.0", "@types/cors": "^2.8.4", "@types/express": "4.17.2", "accepts": "^1.3.5", - "apollo-server-core": "^2.10.1", - "apollo-server-types": "^0.2.10", + "apollo-server-core": "^2.11.0", + "apollo-server-types": "^0.3.0", "body-parser": "^1.18.3", "cors": "^2.8.4", "express": "^4.17.1", @@ -3111,17 +3113,17 @@ } }, "apollo-server-plugin-base": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.10.tgz", - "integrity": "sha512-/xT7UT/tbCDIoTQ4lcEQsJ0ACh7h7QG0BDmeSlDXjwDuENRI50bQ2QoluCMPitZXGe+FCQfLhvzFgzbsZGT0IA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.7.0.tgz", + "integrity": "sha512-//xgYrBYLQSr92W0z3mYsFGoVz3wxKNsv3KcOUBhbOCGTbjZgP7vHOE1vhHhRcpZKKXmjXTVONdrnNJ+XVGi6A==", "requires": { - "apollo-server-types": "^0.2.10" + "apollo-server-types": "^0.3.0" } }, "apollo-server-types": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.2.10.tgz", - "integrity": "sha512-ke9ViPEWfW+2XLe66CaKGVZdS7duSLbamSKSprmmeMBd8s6tmjf0FumUVxV7X4quxPZi0OPo8x0LoLU7GWsmaA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.3.0.tgz", + "integrity": "sha512-FMo7kbTkhph9dfIQ3xDbRLObqmdQH9mwSjxhGsX+JxGMRPPXgd3+GZvCeVKOi/udxh//w1otSeAqItjvbj0tfQ==", "requires": { "apollo-engine-reporting-protobuf": "^0.4.4", "apollo-server-caching": "^0.5.1", @@ -3129,12 +3131,12 @@ } }, "apollo-tracing": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.8.11.tgz", - "integrity": "sha512-Z0wDZ5QOBmpGoajB74ZKGTM7GzG6rqZRzAph4kxud6axcyNqUDKiKZ3Eere+NSLwvvt8M3qnPW4UJSUy/wwOXg==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.9.0.tgz", + "integrity": "sha512-oqspTrf4BLGbKkIk1vF+I31C2v7PPJmF36TFpT/+zJxNvJw54ji4ZMhtytgVqbVldQEintJmdHQIidYBGKmu+g==", "requires": { "apollo-server-env": "^2.4.3", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-upload-client": { @@ -3351,15 +3353,15 @@ "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" }, "babel-eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", - "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" } @@ -3457,12 +3459,12 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bcrypt": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.8.tgz", - "integrity": "sha512-jKV6RvLhI36TQnPDvUFqBEnGX9c8dRRygKxCZu7E+MgLfKZbmmXL8a7/SFFOyHoPNX9nV81cKRC5tbQfvEQtpw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", + "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", "optional": true, "requires": { - "nan": "2.14.0", + "node-addon-api": "^2.0.0", "node-pre-gyp": "0.14.0" } }, @@ -3630,14 +3632,14 @@ } }, "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.0.tgz", + "integrity": "sha512-seffIXhwgB84+OCeT/aMjpZnsAsYDiMSC+CEs3UkF8iU64BZGYcu+TZYs/IBpo4nRi0vJywUJWYdbTsOhFTweg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.361", + "node-releases": "^1.1.50" } }, "bson": { @@ -3776,9 +3778,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001023", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001023.tgz", - "integrity": "sha512-C5TDMiYG11EOhVOA62W1p3UsJ2z4DsHtMBQtjzp3ZsUglcQn62WOUgW0y795c7A5uZ+GCEIvzkMatLIlAsbNTA==", + "version": "1.0.30001030", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz", + "integrity": "sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==", "dev": true }, "caseless": { @@ -3870,9 +3872,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "optional": true }, "ci-info": { @@ -4298,9 +4300,9 @@ } }, "cross-env": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.0.tgz", - "integrity": "sha512-rV6M9ldNgmwP7bx5u6rZsTbYidzwvrwIYZnT08hSGLcQCcggofgFW+sNe7IhA1SRauPS0QuLbbX+wdNtpqE5CQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.1.tgz", + "integrity": "sha512-1+DmLosu38kC4s1H4HzNkcolwdANifu9+5bE6uKQCV4L6jvVdV9qdRAk8vV3GoWRe0x4z+K2fFhgoDMqwNsPqQ==", "dev": true, "requires": { "cross-spawn": "^7.0.1" @@ -4858,9 +4860,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.344", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.344.tgz", - "integrity": "sha512-tvbx2Wl8WBR+ym3u492D0L6/jH+8NoQXqe46+QhbWH3voVPauGuZYeb1QAXYoOAWuiP2dbSvlBx0kQ1F3hu/Mw==", + "version": "1.3.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.363.tgz", + "integrity": "sha512-4w19wPBkeunBjOA53lNFT36IdOD3Tk1OoIDtTX+VToJUUDX42QfuhtsNKXv25wmSnoBOExM3kTbj7/WDNBwHuQ==", "dev": true }, "elegant-spinner": { @@ -5885,9 +5887,9 @@ "dev": true }, "flow-bin": { - "version": "0.118.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.118.0.tgz", - "integrity": "sha512-jlbUu0XkbpXeXhan5xyTqVK1jmEKNxE8hpzznI3TThHTr76GiFwK0iRzhDo4KNy+S9h/KxHaqVhTP86vA6wHCg==", + "version": "0.119.1", + "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.119.1.tgz", + "integrity": "sha512-mX6qjJVi7aLqR9sDf8QIHt8yYEWQbkMLw7qFoC7sM/AbJwvqFm3pATPN96thsaL9o1rrshvxJpSgoj1PJSC3KA==", "dev": true }, "follow-redirects": { @@ -6830,13 +6832,13 @@ } }, "graphql-extensions": { - "version": "0.10.10", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.10.10.tgz", - "integrity": "sha512-pNb1DmUk6vsGtCjCRecpKoXadKNMyKxyLyE9IX65N9aKSmLL+AF7dJOOc4MWhdaAXlzxaDDhe54GpaOfoH7AOw==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.11.0.tgz", + "integrity": "sha512-zd4qfUiJoYBx2MwJusM36SEJ+YmJ1ki8YF8nlm9mgaPDUzsnmFq4lxULxUfhLAXFwZw7MbEN2vV4V6WiNgSJLg==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "apollo-server-env": "^2.4.3", - "apollo-server-types": "^0.2.10" + "apollo-server-types": "^0.3.0" } }, "graphql-list-fields": { @@ -8168,9 +8170,9 @@ } }, "lint-staged": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.7.tgz", - "integrity": "sha512-Byj0F4l7GYUpYYHEqyFH69NiI6ICTg0CeCKbhRorL+ickbzILKUlZLiyCkljZV02wnoh7yH7PmFyYm9PRNwk9g==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.8.tgz", + "integrity": "sha512-Oa9eS4DJqvQMVdywXfEor6F4vP+21fPHF8LUXgBbVWUSWBddjqsvO6Bv1LwMChmgQZZqwUvgJSHlu8HFHAPZmA==", "dev": true, "requires": { "chalk": "^3.0.0", @@ -9032,9 +9034,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "mongodb": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz", - "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.4.tgz", + "integrity": "sha512-xGH41Ig4dkSH5ROGezkgDbsgt/v5zbNUwE3TcFsSbDc6Qn3Qil17dhLsESSDDPTiyFDCPJRpfd4887dtsPgKtA==", "requires": { "bl": "^2.2.0", "bson": "^1.1.1", @@ -9448,9 +9450,9 @@ "optional": true }, "needle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.2.tgz", - "integrity": "sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", + "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", "optional": true, "requires": { "debug": "^3.2.6", @@ -9480,6 +9482,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==", + "optional": true + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -9535,9 +9543,9 @@ } }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", + "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -10172,9 +10180,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.1.tgz", - "integrity": "sha512-1KtKBKg/zWrjEEv//klBbVOPGucuc7HHeJf6OEMueVcUeyF3yueHf+DvhVwBjIAe9/97RAydO/lWjkcMwssuEw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.2.tgz", + "integrity": "sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -10219,12 +10227,12 @@ "integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==" }, "pg-promise": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.4.3.tgz", - "integrity": "sha512-hdZTQ/NlqSlhKM9bMgDhKFiJX+hTuxUygEu1NBjGUtdTdenvnrxjaheeob6OEIxiYSmZFMG8uIsoVCIIPMgCsg==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.4.4.tgz", + "integrity": "sha512-N2NsOgKxrnNPwP0Q609ZmxmAZEo2TQ26SzSvlbZWQb8vteqUhOPpU/pHi9DGatJrPcXNoyr4xjRw42CNfEBg/w==", "requires": { "assert-options": "0.6.1", - "pg": "7.18.1", + "pg": "7.18.2", "pg-minify": "1.5.2", "spex": "3.0.1" } @@ -10640,9 +10648,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", - "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz", + "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==", "dev": true, "requires": { "jsesc": "~0.5.0" diff --git a/package.json b/package.json index 52004a063f..d5e58a5103 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "3.10.0", + "version": "4.1.0", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -24,7 +24,7 @@ "@parse/push-adapter": "3.2.0", "@parse/s3-files-adapter": "1.4.0", "@parse/simple-mailgun-adapter": "1.1.0", - "apollo-server-express": "2.10.1", + "apollo-server-express": "2.11.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "commander": "4.1.1", @@ -43,10 +43,10 @@ "lodash": "4.17.15", "lru-cache": "5.1.1", "mime": "2.4.4", - "mongodb": "3.5.3", + "mongodb": "3.5.4", "node-rsa": "1.0.7", "parse": "2.11.0", - "pg-promise": "10.4.3", + "pg-promise": "10.4.4", "pluralize": "^8.0.0", "redis": "3.0.0", "semver": "7.1.3", @@ -59,10 +59,10 @@ }, "devDependencies": { "@babel/cli": "7.8.4", - "@babel/core": "7.8.4", + "@babel/core": "7.8.6", "@babel/plugin-proposal-object-rest-spread": "7.8.3", "@babel/plugin-transform-flow-strip-types": "7.8.3", - "@babel/preset-env": "7.8.4", + "@babel/preset-env": "7.8.6", "@parse/minami": "1.0.0", "apollo-cache-inmemory": "1.6.5", "apollo-client": "2.6.6", @@ -71,13 +71,13 @@ "apollo-link-ws": "1.0.19", "apollo-upload-client": "12.1.0", "apollo-utilities": "1.3.3", - "babel-eslint": "10.0.3", + "babel-eslint": "10.1.0", "bcrypt-nodejs": "0.0.3", - "cross-env": "7.0.0", + "cross-env": "7.0.1", "deep-diff": "1.0.2", "eslint": "6.8.0", "eslint-plugin-flowtype": "4.5.0", - "flow-bin": "0.118.0", + "flow-bin": "0.119.1", "form-data": "3.0.0", "gaze": "1.1.3", "graphql-tag": "^2.10.1", @@ -85,7 +85,7 @@ "jasmine": "3.5.0", "jsdoc": "3.6.3", "jsdoc-babel": "0.5.0", - "lint-staged": "10.0.7", + "lint-staged": "10.0.8", "mongodb-runner": "4.8.0", "node-fetch": "2.6.0", "nyc": "15.0.0", @@ -114,7 +114,7 @@ "parse-server": "./bin/parse-server" }, "optionalDependencies": { - "bcrypt": "3.0.8" + "bcrypt": "4.0.1" }, "collective": { "type": "opencollective", diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index 70b554f83a..af373e64f2 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -3,118 +3,56 @@ const validateQuery = DatabaseController._validateQuery; describe('DatabaseController', function() { describe('validateQuery', function() { - describe('with skipMongoDBServer13732Workaround disabled (the default)', function() { - it('should restructure simple cases of SERVER-13732', done => { - const query = { - $or: [{ a: 1 }, { a: 2 }], - _rperm: { $in: ['a', 'b'] }, - foo: 3, - }; - validateQuery(query, false); - expect(query).toEqual({ - $or: [ - { a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 }, - { a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 }, - ], - }); - done(); - }); - - it('should not restructure SERVER-13732 queries with $nears', done => { - let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } }; - validateQuery(query, false); - expect(query).toEqual({ - $or: [{ a: 1 }, { b: 1 }], - c: { $nearSphere: {} }, - }); - query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }; - validateQuery(query, false); - expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }); - done(); - }); - - it('should push refactored keys down a tree for SERVER-13732', done => { - const query = { - a: 1, - $or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }], - }; - validateQuery(query, false); - expect(query).toEqual({ - $or: [ - { $or: [{ b: 1, a: 1 }, { b: 2, a: 1 }] }, - { $or: [{ c: 1, a: 1 }, { c: 2, a: 1 }] }, - ], - }); - done(); - }); - - it('should reject invalid queries', done => { - expect(() => validateQuery({ $or: { a: 1 } }, false)).toThrow(); - done(); - }); - - it('should accept valid queries', done => { - expect(() => - validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, false) - ).not.toThrow(); - done(); - }); + it('should not restructure simple cases of SERVER-13732', done => { + const query = { + $or: [{ a: 1 }, { a: 2 }], + _rperm: { $in: ['a', 'b'] }, + foo: 3, + }; + validateQuery(query); + expect(query).toEqual({ + $or: [{ a: 1 }, { a: 2 }], + _rperm: { $in: ['a', 'b'] }, + foo: 3, + }); + done(); }); - describe('with skipMongoDBServer13732Workaround enabled', function() { - it('should not restructure simple cases of SERVER-13732', done => { - const query = { - $or: [{ a: 1 }, { a: 2 }], - _rperm: { $in: ['a', 'b'] }, - foo: 3, - }; - validateQuery(query, true); - expect(query).toEqual({ - $or: [{ a: 1 }, { a: 2 }], - _rperm: { $in: ['a', 'b'] }, - foo: 3, - }); - done(); - }); + it('should not restructure SERVER-13732 queries with $nears', done => { + let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } }; + validateQuery(query); + expect(query).toEqual({ + $or: [{ a: 1 }, { b: 1 }], + c: { $nearSphere: {} }, + }); + query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }; + validateQuery(query); + expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }); + done(); + }); - it('should not restructure SERVER-13732 queries with $nears', done => { - let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } }; - validateQuery(query, true); - expect(query).toEqual({ - $or: [{ a: 1 }, { b: 1 }], - c: { $nearSphere: {} }, - }); - query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }; - validateQuery(query, true); - expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } }); - done(); + it('should not push refactored keys down a tree for SERVER-13732', done => { + const query = { + a: 1, + $or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }], + }; + validateQuery(query); + expect(query).toEqual({ + a: 1, + $or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }], }); - it('should not push refactored keys down a tree for SERVER-13732', done => { - const query = { - a: 1, - $or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }], - }; - validateQuery(query, true); - expect(query).toEqual({ - a: 1, - $or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }], - }); - - done(); - }); + done(); + }); - it('should reject invalid queries', done => { - expect(() => validateQuery({ $or: { a: 1 } }, true)).toThrow(); - done(); - }); + it('should reject invalid queries', done => { + expect(() => validateQuery({ $or: { a: 1 } })).toThrow(); + done(); + }); - it('should accept valid queries', done => { - expect(() => - validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, true) - ).not.toThrow(); - done(); - }); + it('should accept valid queries', done => { + expect(() => validateQuery({ $or: [{ a: 1 }, { b: 2 }] })).not.toThrow(); + done(); }); }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index ca08f227db..e9cbc64dc2 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -5,6 +5,8 @@ const fetch = require('node-fetch'); const FormData = require('form-data'); const ws = require('ws'); require('./helper'); +const { updateCLP } = require('./dev'); + const pluralize = require('pluralize'); const { getMainDefinition } = require('apollo-utilities'); const { ApolloLink, split } = require('apollo-link'); @@ -15,6 +17,14 @@ const { SubscriptionClient } = require('subscriptions-transport-ws'); const { WebSocketLink } = require('apollo-link-ws'); const ApolloClient = require('apollo-client').default; const gql = require('graphql-tag'); +const { + GraphQLObjectType, + GraphQLString, + GraphQLNonNull, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLSchema, +} = require('graphql'); const { ParseServer } = require('../'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const ReadPreference = require('mongodb').ReadPreference; @@ -4632,6 +4642,84 @@ describe('ParseGraphQLServer', () => { ).toBeDefined(); }); + it('should respect protectedFields', async done => { + await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const className = 'GraphQLClass'; + + await updateCLP( + { + get: { '*': true }, + find: { '*': true }, + + protectedFields: { + '*': ['someField', 'someOtherField'], + authenticated: ['someField'], + 'userField:pointerToUser': [], + [user2.id]: [], + }, + }, + className + ); + + const getObject = async (className, id, user) => { + const headers = user + ? { ['X-Parse-Session-Token']: user.getSessionToken() } + : undefined; + + const specificQueryResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + get: graphQLClass(id: $id) { + pointerToUser { + username + id + } + someField + someOtherField + } + } + `, + variables: { + id: id, + }, + context: { + headers: headers, + }, + }); + + return specificQueryResult.data.get; + }; + + const id = object3.id; + + /* not authenticated */ + const objectPublic = await getObject(className, id, undefined); + + expect(objectPublic.someField).toBeNull(); + expect(objectPublic.someOtherField).toBeNull(); + + /* authenticated */ + const objectAuth = await getObject(className, id, user1); + + expect(objectAuth.someField).toBeNull(); + expect(objectAuth.someOtherField).toBe('B'); + + /* pointer field */ + const objectPointed = await getObject(className, id, user5); + + expect(objectPointed.someField).toBe('someValue3'); + expect(objectPointed.someOtherField).toBe('B'); + + /* for user id */ + const objectForUser = await getObject(className, id, user2); + + expect(objectForUser.someField).toBe('someValue3'); + expect(objectForUser.someOtherField).toBe('B'); + + done(); + }); describe_only_db('mongo')('read preferences', () => { it('should read from primary by default', async () => { try { @@ -10514,130 +10602,296 @@ describe('ParseGraphQLServer', () => { }); describe('Custom API', () => { - let httpServer; - const headers = { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Javascript-Key': 'test', - }; - let apolloClient; - - beforeAll(async () => { - const expressApp = express(); - httpServer = http.createServer(expressApp); - parseGraphQLServer = new ParseGraphQLServer(parseServer, { - graphQLPath: '/graphql', - graphQLCustomTypeDefs: gql` - extend type Query { - hello: String @resolve - hello2: String @resolve(to: "hello") - userEcho(user: CreateUserFieldsInput!): User! @resolve - hello3: String! @mock(with: "Hello world!") - hello4: User! @mock(with: { username: "somefolk" }) - } - `, - }); - parseGraphQLServer.applyGraphQL(expressApp); - await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); - const httpLink = createUploadLink({ - uri: 'http://localhost:13377/graphql', - fetch, - headers, - }); - apolloClient = new ApolloClient({ - link: httpLink, - cache: new InMemoryCache(), - defaultOptions: { - query: { - fetchPolicy: 'no-cache', + describe('GraphQL Schema Based', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: gql` + extend type Query { + hello: String @resolve + hello2: String @resolve(to: "hello") + userEcho(user: CreateUserFieldsInput!): User! @resolve + hello3: String! @mock(with: "Hello world!") + hello4: User! @mock(with: { username: "somefolk" }) + } + `, + }); + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, }, - }, + }); }); - }); - - afterAll(async () => { - await httpServer.close(); - }); - it('can resolve a custom query using default function name', async () => { - Parse.Cloud.define('hello', async () => { - return 'Hello world!'; + afterAll(async () => { + await httpServer.close(); }); - const result = await apolloClient.query({ - query: gql` - query Hello { - hello - } - `, - }); + it('can resolve a custom query using default function name', async () => { + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); - expect(result.data.hello).toEqual('Hello world!'); - }); + const result = await apolloClient.query({ + query: gql` + query Hello { + hello + } + `, + }); - it('can resolve a custom query using function name set by "to" argument', async () => { - Parse.Cloud.define('hello', async () => { - return 'Hello world!'; + expect(result.data.hello).toEqual('Hello world!'); }); - const result = await apolloClient.query({ - query: gql` - query Hello { - hello2 - } - `, - }); + it('can resolve a custom query using function name set by "to" argument', async () => { + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); + + const result = await apolloClient.query({ + query: gql` + query Hello { + hello2 + } + `, + }); - expect(result.data.hello2).toEqual('Hello world!'); + expect(result.data.hello2).toEqual('Hello world!'); + }); }); - it('should resolve auto types', async () => { - Parse.Cloud.define('userEcho', async req => { - return req.params.user; + describe('SDL Based', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; + + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + const TypeEnum = new GraphQLEnumType({ + name: 'TypeEnum', + values: { + human: { value: 'human' }, + robot: { value: 'robot' }, + }, + }); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + customQuery: { + type: new GraphQLNonNull(GraphQLString), + args: { + message: { type: new GraphQLNonNull(GraphQLString) }, + }, + resolve: (p, { message }) => message, + }, + }, + }), + types: [ + new GraphQLInputObjectType({ + name: 'CreateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + new GraphQLInputObjectType({ + name: 'UpdateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + new GraphQLObjectType({ + name: 'SomeClass', + fields: { + nameUpperCase: { + type: new GraphQLNonNull(GraphQLString), + resolve: p => p.name.toUpperCase(), + }, + type: { type: TypeEnum }, + language: { + type: new GraphQLEnumType({ + name: 'LanguageEnum', + values: { + fr: { value: 'fr' }, + en: { value: 'en' }, + }, + }), + resolve: () => 'fr', + }, + }, + }), + ], + }), + }); + + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, + }, + }); + }); + + afterAll(async () => { + await httpServer.close(); }); - const result = await apolloClient.query({ - query: gql` - query UserEcho($user: CreateUserFieldsInput!) { - userEcho(user: $user) { - username + it('can resolve a custom query', async () => { + const result = await apolloClient.query({ + variables: { message: 'hello' }, + query: gql` + query CustomQuery($message: String!) { + customQuery(message: $message) } - } - `, - variables: { - user: { - username: 'somefolk', - password: 'somepassword', - }, - }, + `, + }); + expect(result.data.customQuery).toEqual('hello'); }); - expect(result.data.userEcho.username).toEqual('somefolk'); + it('can resolve a custom extend type', async () => { + const obj = new Parse.Object('SomeClass'); + await obj.save({ name: 'aname', type: 'robot' }); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const result = await apolloClient.query({ + variables: { id: obj.id }, + query: gql` + query someClass($id: ID!) { + someClass(id: $id) { + nameUpperCase + language + type + } + } + `, + }); + expect(result.data.someClass.nameUpperCase).toEqual('ANAME'); + expect(result.data.someClass.language).toEqual('fr'); + expect(result.data.someClass.type).toEqual('robot'); + + const result2 = await apolloClient.query({ + variables: { id: obj.id }, + query: gql` + query someClass($id: ID!) { + someClass(id: $id) { + name + language + } + } + `, + }); + expect(result2.data.someClass.name).toEqual('aname'); + expect(result.data.someClass.language).toEqual('fr'); + const result3 = await apolloClient.mutate({ + variables: { id: obj.id, name: 'anewname' }, + mutation: gql` + mutation someClass($id: ID!, $name: String!) { + updateSomeClass( + input: { id: $id, fields: { name: $name, type: human } } + ) { + someClass { + nameUpperCase + type + } + } + } + `, + }); + expect(result3.data.updateSomeClass.someClass.nameUpperCase).toEqual( + 'ANEWNAME' + ); + expect(result3.data.updateSomeClass.someClass.type).toEqual('human'); + }); }); + describe('Async Function Based Merge', () => { + let httpServer; + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }; + let apolloClient; - it('can mock a custom query with string', async () => { - const result = await apolloClient.query({ - query: gql` - query Hello { - hello3 - } - `, + beforeAll(async () => { + const expressApp = express(); + httpServer = http.createServer(expressApp); + parseGraphQLServer = new ParseGraphQLServer(parseServer, { + graphQLPath: '/graphql', + graphQLCustomTypeDefs: ({ autoSchema, mergeSchemas }) => + mergeSchemas({ schemas: [autoSchema] }), + }); + + parseGraphQLServer.applyGraphQL(expressApp); + await new Promise(resolve => + httpServer.listen({ port: 13377 }, resolve) + ); + const httpLink = createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, + }, + }); }); - expect(result.data.hello3).toEqual('Hello world!'); - }); + afterAll(async () => { + await httpServer.close(); + }); - it('can mock a custom query with auto type', async () => { - const result = await apolloClient.query({ - query: gql` - query Hello { - hello4 { - username + it('can resolve a query', async () => { + const result = await apolloClient.query({ + query: gql` + query Health { + health } - } - `, + `, + }); + expect(result.data.health).toEqual(true); }); - - expect(result.data.hello4.username).toEqual('somefolk'); }); }); }); diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 74faa9aedc..fa83588b9d 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -24,6 +24,43 @@ describe('ParseLiveQuery', function() { await object.save(); }); + it('handle invalid websocket payload length', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + websocketTimeout: 100, + }); + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + + // All control frames must have a payload length of 125 bytes or less. + // https://tools.ietf.org/html/rfc6455#section-5.5 + // + // 0x89 = 10001001 = ping + // 0xfe = 11111110 = first bit is masking the remaining 7 are 1111110 or 126 the payload length + // https://tools.ietf.org/html/rfc6455#section-5.2 + const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); + client.socket._socket.write(Buffer.from([0x89, 0xfe])); + + subscription.on('update', async object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + // Wait for Websocket timeout to reconnect + setTimeout(async () => { + object.set({ foo: 'bar' }); + await object.save(); + }, 1000); + }); + afterEach(async function(done) { const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); client.close(); diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 260a48f217..ee4727404a 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -4868,4 +4868,36 @@ describe('Parse.Query testing', () => { const results = await query.find(); equal(results[0].get('array').length, 105); }); + + it('exclude keys (sdk query)', async done => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const query = new Parse.Query('TestObject'); + query.exclude('foo'); + + const object = await query.get(obj.id); + expect(object.get('foo')).toBeUndefined(); + expect(object.get('hello')).toBe('world'); + done(); + }); + + xit('todo: exclude keys with select key (sdk query get)', async done => { + // there is some problem with js sdk caching + + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const query = new Parse.Query('TestObject'); + + query.withJSON({ + keys: 'hello', + excludeKeys: 'hello', + }); + + const object = await query.get(obj.id); + expect(object.get('foo')).toBeUndefined(); + expect(object.get('hello')).toBeUndefined(); + done(); + }); }); diff --git a/spec/ParseWebSocketServer.spec.js b/spec/ParseWebSocketServer.spec.js index c9416f4c08..7bf8a5c57d 100644 --- a/spec/ParseWebSocketServer.spec.js +++ b/spec/ParseWebSocketServer.spec.js @@ -1,11 +1,12 @@ const { ParseWebSocketServer, } = require('../lib/LiveQuery/ParseWebSocketServer'); +const EventEmitter = require('events'); describe('ParseWebSocketServer', function() { beforeEach(function(done) { // Mock ws server - const EventEmitter = require('events'); + const mockServer = function() { return new EventEmitter(); }; @@ -22,11 +23,11 @@ describe('ParseWebSocketServer', function() { onConnectCallback, { websocketTimeout: 5 } ).server; - const ws = { - readyState: 0, - OPEN: 0, - ping: jasmine.createSpy('ping'), - }; + const ws = new EventEmitter(); + ws.readyState = 0; + ws.OPEN = 0; + ws.ping = jasmine.createSpy('ping'); + parseWebSocketServer.onConnection(ws); // Make sure callback is called diff --git a/spec/PointerPermissions.spec.js b/spec/PointerPermissions.spec.js index bd7e34b308..a3a1b9f498 100644 --- a/spec/PointerPermissions.spec.js +++ b/spec/PointerPermissions.spec.js @@ -2047,7 +2047,7 @@ describe('Pointer Permissions', () => { } async function logIn(userObject) { - await Parse.User.logIn(userObject.getUsername(), 'password'); + return await Parse.User.logIn(userObject.getUsername(), 'password'); } async function updateCLP(clp) { @@ -3098,5 +3098,55 @@ describe('Pointer Permissions', () => { done(); }); }); + + describe('using pointer-fields and queries with keys projection', () => { + let user1; + /** + * owner: user1 + * + * testers: [user1] + */ + let obj; + + /** + * Clear cache, create user and object, login user + */ + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + user1 = await createUser('user1'); + user1 = await logIn(user1); + + obj = new Parse.Object(className); + + obj.set('owner', user1); + obj.set('field', 'field'); + obj.set('test', 'test'); + + await Parse.Object.saveAll([obj], { useMasterKey: true }); + + await obj.fetch(); + } + + beforeEach(async () => { + await initialize(); + }); + + it('should be enforced regardless of pointer-field being included in keys (select)', async done => { + await updateCLP({ + get: { '*': true }, + find: { pointerFields: ['owner'] }, + update: { pointerFields: ['owner'] }, + }); + + const query = new Parse.Query('AnObject'); + query.select('field', 'test'); + + const [object] = await query.find({ objectId: obj.id }); + expect(object.get('field')).toBe('field'); + expect(object.get('test')).toBe('test'); + done(); + }); + }); }); }); diff --git a/spec/ProtectedFields.spec.js b/spec/ProtectedFields.spec.js index 78e44f3bc1..fdc3c2d10a 100644 --- a/spec/ProtectedFields.spec.js +++ b/spec/ProtectedFields.spec.js @@ -1,5 +1,13 @@ const Config = require('../lib/Config'); const Parse = require('parse/node'); +const request = require('../lib/request'); +const { + className, + createRole, + createUser, + logIn, + updateCLP, +} = require('./dev'); describe('ProtectedFields', function() { it('should handle and empty protectedFields', async function() { @@ -310,7 +318,7 @@ describe('ProtectedFields', function() { done(); }); - it('should create merge protected fields when using multiple pointer-permission fields', async done => { + it('should intersect protected fields when using multiple pointer-permission fields', async done => { const config = Config.get(Parse.applicationId); const obj = new Parse.Object('AnObject'); @@ -327,8 +335,8 @@ describe('ProtectedFields', function() { get: { '*': true }, find: { '*': true }, protectedFields: { - '*': [], - 'userField:owners': ['owners'], + '*': ['owners', 'owner', 'test'], + 'userField:owners': ['owners', 'owner'], 'userField:owner': ['owner'], }, } @@ -337,7 +345,7 @@ describe('ProtectedFields', function() { // Check if protectFields from pointer-permissions got combined await Parse.User.logIn('user1', 'password'); const objectAgain = await obj.fetch(); - expect(objectAgain.get('owners')).toBe(undefined); + expect(objectAgain.get('owners').length).toBe(1); expect(objectAgain.get('owner')).toBe(undefined); expect(objectAgain.get('test')).toBe('test'); done(); @@ -605,7 +613,7 @@ describe('ProtectedFields', function() { done(); }); - it('should create merge protected fields when using multiple pointer-permission fields', async done => { + it('should intersect protected fields when using multiple pointer-permission fields', async done => { const config = Config.get(Parse.applicationId); const obj = new Parse.Object('AnObject'); const obj2 = new Parse.Object('AnObject'); @@ -614,7 +622,6 @@ describe('ProtectedFields', function() { obj.set('owner', user1); obj.set('test', 'test'); obj2.set('owners', [user1]); - obj2.set('owner', user1); obj2.set('test', 'test2'); await Parse.Object.saveAll([obj, obj2]); @@ -626,8 +633,8 @@ describe('ProtectedFields', function() { get: { '*': true }, find: { '*': true }, protectedFields: { - '*': [], - 'userField:owners': ['owners'], + '*': ['owners', 'owner', 'test'], + 'userField:owners': ['owners', 'owner'], 'userField:owner': ['owner'], }, } @@ -642,7 +649,7 @@ describe('ProtectedFields', function() { results.sort((a, b) => a.get('test').localeCompare(b.get('test'))); expect(results.length).toBe(2); - expect(results[0].get('owners')).toBe(undefined); + expect(results[0].get('owners').length).toBe(1); expect(results[0].get('owner')).toBe(undefined); expect(results[0].get('test')).toBe('test'); expect(results[1].get('owners')).toBe(undefined); @@ -760,20 +767,24 @@ describe('ProtectedFields', function() { }); describe('schema setup', () => { - const className = 'AObject'; - async function updateCLP(clp) { - const config = Config.get(Parse.applicationId); - const schemaController = await config.database.loadSchema(); + let object; - await schemaController.updateClass(className, {}, clp); + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + object = new Parse.Object(className); + + object.set('revision', 0); + object.set('test', 'test'); + + await object.save(null, { useMasterKey: true }); } - it('should fail setting non-existing protected field', async () => { - const object = new Parse.Object(className, { - revision: 0, - }); - await object.save(); + beforeEach(async () => { + await initialize(); + }); + it('should fail setting non-existing protected field', async done => { const field = 'non-existing'; const entity = '*'; @@ -789,6 +800,936 @@ describe('ProtectedFields', function() { `Field '${field}' in protectedFields:${entity} does not exist` ) ); + done(); + }); + + it('should allow setting authenticated', async () => { + await expectAsync( + updateCLP({ + protectedFields: { + authenticated: ['test'], + }, + }) + ).toBeResolved(); + }); + + it('should not allow protecting default fields', async () => { + const defaultFields = ['objectId', 'createdAt', 'updatedAt', 'ACL']; + for (const field of defaultFields) { + await expectAsync( + updateCLP({ + protectedFields: { + '*': [field], + }, + }) + ).toBeRejectedWith( + new Parse.Error( + Parse.Error.INVALID_JSON, + `Default field '${field}' can not be protected` + ) + ); + } + }); + }); + + describe('targeting public access', () => { + let obj1; + + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + obj1 = new Parse.Object(className); + + obj1.set('foo', 'foo'); + obj1.set('bar', 'bar'); + obj1.set('qux', 'qux'); + + await obj1.save(null, { + useMasterKey: true, + }); + } + + beforeEach(async () => { + await initialize(); + }); + + it('should hide field', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['foo'], + }, + }); + + // unauthenticated + const object = await obj1.fetch(); + + expect(object.get('foo')).toBe(undefined); + expect(object.get('bar')).toBeDefined(); + expect(object.get('qux')).toBeDefined(); + + done(); + }); + + it('should hide mutiple fields', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['foo', 'bar'], + }, + }); + + // unauthenticated + const object = await obj1.fetch(); + + expect(object.get('foo')).toBe(undefined); + expect(object.get('bar')).toBe(undefined); + expect(object.get('qux')).toBeDefined(); + + done(); + }); + + it('should not hide any fields when set as empty array', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': [], + }, + }); + + // unauthenticated + const object = await obj1.fetch(); + + expect(object.get('foo')).toBeDefined(); + expect(object.get('bar')).toBeDefined(); + expect(object.get('qux')).toBeDefined(); + expect(object.id).toBeDefined(); + expect(object.createdAt).toBeDefined(); + expect(object.updatedAt).toBeDefined(); + expect(object.getACL()).toBeDefined(); + + done(); + }); + }); + + describe('targeting authenticated', () => { + /** + * is **owner** of: _obj1_ + * + * is **tester** of: [ _obj1, obj2_ ] + */ + let user1; + + /** + * is **owner** of: _obj2_ + * + * is **tester** of: [ _obj1_ ] + */ + let user2; + + /** + * **owner**: _user1_ + * + * **testers**: [ _user1,user2_ ] + */ + let obj1; + + /** + * **owner**: _user2_ + * + * **testers**: [ _user1_ ] + */ + let obj2; + + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + await Parse.User.logOut(); + + [user1, user2] = await Promise.all([ + createUser('user1'), + createUser('user2'), + ]); + + obj1 = new Parse.Object(className); + obj2 = new Parse.Object(className); + + obj1.set('owner', user1); + obj1.set('testers', [user1, user2]); + obj1.set('test', 'test'); + + obj2.set('owner', user2); + obj2.set('testers', [user1]); + obj2.set('test', 'test'); + + await Parse.Object.saveAll([obj1, obj2], { + useMasterKey: true, + }); + } + + beforeEach(async () => { + await initialize(); + }); + + it('should not hide any fields when set as empty array', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: [], + }, + }); + + // authenticated + await logIn(user1); + + const object = await obj1.fetch(); + + expect(object.get('owner')).toBeDefined(); + expect(object.get('testers')).toBeDefined(); + expect(object.get('test')).toBeDefined(); + expect(object.id).toBeDefined(); + expect(object.createdAt).toBeDefined(); + expect(object.updatedAt).toBeDefined(); + expect(object.getACL()).toBeDefined(); + + done(); + }); + + it('should hide fields for authenticated users only (* not set)', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['test'], + }, + }); + + // not authenticated + const objectNonAuth = await obj1.fetch(); + + expect(objectNonAuth.get('test')).toBeDefined(); + + // authenticated + await logIn(user1); + const object = await obj1.fetch(); + + expect(object.get('test')).toBe(undefined); + + done(); + }); + + it('should intersect public and auth for authenticated user', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['owner', 'testers'], + authenticated: ['testers'], + }, + }); + + // authenticated + await logIn(user1); + const objectAuth = await obj1.fetch(); + + // ( {A,B} intersect {B} ) == {B} + + expect(objectAuth.get('testers')).not.toBeDefined( + 'Should not be visible - protected for * and authenticated' + ); + expect(objectAuth.get('test')).toBeDefined( + 'Should be visible - not protected for everyone (* and authenticated)' + ); + expect(objectAuth.get('owner')).toBeDefined( + 'Should be visible - not protected for authenticated' + ); + + done(); + }); + + it('should have higher prio than public for logged in users (intersect)', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['test'], + authenticated: [], + }, + }); + // authenticated, permitted + await logIn(user1); + + const object = await obj1.fetch(); + expect(object.get('test')).toBe('test'); + + done(); + }); + + it('should have no effect on unauthenticated users (public not set)', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['test'], + }, + }); + + // unauthenticated, protected + const objectNonAuth = await obj1.fetch(); + expect(objectNonAuth.get('test')).toBe('test'); + + done(); + }); + + it('should protect multiple fields for authenticated users', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['test', 'owner'], + }, + }); + + // authenticated + await logIn(user1); + const object = await obj1.fetch(); + + expect(object.get('test')).toBe(undefined); + expect(object.get('owner')).toBe(undefined); + + done(); + }); + + it('should not be affected by rules not applicable to user (smoke)', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['owner', 'testers'], + [`role:${roleName}`]: ['test'], + 'userField:owner': [], + [user1.id]: [], + }, + }); + + // authenticated, non-owner, no role + await logIn(user2); + const objectNotOwned = await obj1.fetch(); + + expect(objectNotOwned.get('owner')).toBe(undefined); + expect(objectNotOwned.get('testers')).toBe(undefined); + expect(objectNotOwned.get('test')).toBeDefined(); + + done(); + }); + }); + + describe('targeting roles', () => { + let user1, user2; + + /** + * owner: user1 + * + * testers: [user1,user2] + */ + let obj1; + + /** + * owner: user2 + * + * testers: [user1] + */ + let obj2; + + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + [user1, user2] = await Promise.all([ + createUser('user1'), + createUser('user2'), + ]); + + obj1 = new Parse.Object(className); + obj2 = new Parse.Object(className); + + obj1.set('owner', user1); + obj1.set('testers', [user1, user2]); + obj1.set('test', 'test'); + + obj2.set('owner', user2); + obj2.set('testers', [user1]); + obj2.set('test', 'test'); + + await Parse.Object.saveAll([obj1, obj2], { + useMasterKey: true, + }); + } + + beforeEach(async () => { + await initialize(); + }); + + it('should hide field when user belongs to a role', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + protectedFields: { + [`role:${roleName}`]: ['test'], + }, + get: { '*': true }, + find: { '*': true }, + }); + + // user has role + await logIn(user1); + + const object = await obj1.fetch(); + expect(object.get('test')).toBe(undefined); // field protected + expect(object.get('owner')).toBeDefined(); + expect(object.get('testers')).toBeDefined(); + + done(); + }); + + it('should not hide any fields when set as empty array', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + protectedFields: { + [`role:${roleName}`]: [], + }, + get: { '*': true }, + find: { '*': true }, + }); + + // user has role + await logIn(user1); + + const object = await obj1.fetch(); + + expect(object.get('owner')).toBeDefined(); + expect(object.get('testers')).toBeDefined(); + expect(object.get('test')).toBeDefined(); + expect(object.id).toBeDefined(); + expect(object.createdAt).toBeDefined(); + expect(object.updatedAt).toBeDefined(); + expect(object.getACL()).toBeDefined(); + + done(); + }); + + it('should hide multiple fields when user belongs to a role', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + [`role:${roleName}`]: ['test', 'owner'], + }, + }); + + // user has role + await logIn(user1); + + const object = await obj1.fetch(); + + expect(object.get('test')).toBe( + undefined, + 'Field should not be visible - protected by role' + ); + expect(object.get('owner')).toBe( + undefined, + 'Field should not be visible - protected by role' + ); + expect(object.get('testers')).toBeDefined(); + + done(); + }); + + it('should not protect when user does not belong to a role', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + [`role:${roleName}`]: ['test', 'owner'], + }, + }); + + // user doesn't have role + await logIn(user2); + const object = await obj1.fetch(); + + expect(object.get('test')).toBeDefined(); + expect(object.get('owner')).toBeDefined(); + expect(object.get('testers')).toBeDefined(); + + done(); + }); + + it('should intersect protected fields when user belongs to multiple roles', async done => { + const role1 = await createRole({ users: user1 }); + const role2 = await createRole({ users: user1 }); + + const role1name = role1.get('name'); + const role2name = role2.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + [`role:${role1name}`]: ['owner'], + [`role:${role2name}`]: ['test', 'owner'], + }, + }); + + // user has both roles + await logIn(user1); + const object = await obj1.fetch(); + + // "owner" is a result of intersection + expect(object.get('owner')).toBe( + undefined, + 'Must not be visible - protected for all roles the user belongs to' + ); + expect(object.get('test')).toBeDefined( + 'Has to be visible - is not protected for users with role1' + ); + done(); + }); + + it('should intersect protected fields when user belongs to multiple roles hierarchy', async done => { + const admin = await createRole({ + users: user1, + roleName: 'admin', + }); + + const moder = await createRole({ + users: [user1, user2], + roleName: 'moder', + }); + + const tester = await createRole({ + roleName: 'tester', + }); + + // admin supersets moder role + moder.relation('roles').add(admin); + await moder.save(null, { useMasterKey: true }); + + tester.relation('roles').add(moder); + await tester.save(null, { useMasterKey: true }); + + const roleAdmin = `role:${admin.get('name')}`; + const roleModer = `role:${moder.get('name')}`; + const roleTester = `role:${tester.get('name')}`; + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + [roleAdmin]: [], + [roleModer]: ['owner'], + [roleTester]: ['test', 'owner'], + }, + }); + + // user1 has admin & moder & tester roles, (moder includes tester). + await logIn(user1); + const object = await obj1.fetch(); + + // being admin makes all fields visible + expect(object.get('test')).toBeDefined( + 'Should be visible - admin role explicitly removes protection for all fields ( [] )' + ); + expect(object.get('owner')).toBeDefined( + 'Should be visible - admin role explicitly removes protection for all fields ( [] )' + ); + + // user2 has moder & tester role, moder includes tester. + await logIn(user2); + const objectAgain = await obj1.fetch(); + + // being moder allows "test" field + expect(objectAgain.get('owner')).toBe( + undefined, + '"owner" should not be visible - protected for each role user belongs to' + ); + expect(objectAgain.get('test')).toBeDefined( + 'Should be visible - moder role does not protect "test" field' + ); + + done(); + }); + + it('should be able to clear protected fields for role (protected for authenticated)', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['test'], + [`role:${roleName}`]: [], + }, + }); + + // user has role, test field visible + await logIn(user1); + const object = await obj1.fetch(); + expect(object.get('test')).toBe('test'); + + done(); + }); + + it('should determine protectedFields as intersection of field sets for public and role', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['test', 'owner'], + [`role:${roleName}`]: ['owner', 'testers'], + }, + }); + + // user has role + await logIn(user1); + + const object = await obj1.fetch(); + expect(object.get('test')).toBeDefined( + 'Should be visible - "test" is not protected for role user belongs to' + ); + expect(object.get('testers')).toBeDefined( + 'Should be visible - "testers" is allowed for everyone (*)' + ); + expect(object.get('owner')).toBe( + undefined, + 'Should not be visible - "test" is not allowed for both public(*) and role' + ); + done(); + }); + + it('should be determined as an intersection of protecedFields for authenticated and role', async done => { + const role = await createRole({ users: user1 }); + const roleName = role.get('name'); + + // this is an example of misunderstood configuration. + // If you allow (== do not restrict) some field for broader audience + // (having a role implies user inheres to 'authenticated' group) + // it's not possible to narrow by protecting field for a role. + // You'd have to protect it for 'authenticated' as well. + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + authenticated: ['test'], + [`role:${roleName}`]: ['owner'], + }, + }); + + // user has role + await logIn(user1); + const object = await obj1.fetch(); + + // + expect(object.get('test')).toBeDefined( + "Being both auhenticated and having a role leads to clearing protection on 'test' (by role rules)" + ); + expect(object.get('owner')).toBeDefined( + 'All authenticated users allowed to see "owner"' + ); + expect(object.get('testers')).toBeDefined(); + + done(); + }); + + it('should not hide fields when user does not belong to a role protectedFields set for', async done => { + const role = await createRole({ users: user2 }); + const roleName = role.get('name'); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + [`role:${roleName}`]: ['test'], + }, + }); + + // relate user1 to some role, no protectedFields for it + await createRole({ users: user1 }); + + await logIn(user1); + + const object = await obj1.fetch(); + expect(object.get('test')).toBeDefined( + 'Field should be visible - user belongs to a role that has no protectedFields set' + ); + + done(); + }); + }); + + describe('using pointer-fields and queries with keys projection', () => { + /* + * Pointer variant ("userField:column") relies on User ids + * returned after query executed (hides fields before sending it to client) + * If such column is excluded/not included (not returned from db because of 'project') + * there will be no user ids to check against + * and protectedFields won't be applied correctly. + */ + + let user1; + /** + * owner: user1 + * + * testers: [user1] + */ + let obj; + + let headers; + + /** + * Clear cache, create user and object, login user and setup rest headers with token + */ + async function initialize() { + await Config.get(Parse.applicationId).database.schemaCache.clear(); + + user1 = await createUser('user1'); + user1 = await logIn(user1); + + // await user1.fetch(); + obj = new Parse.Object(className); + + obj.set('owner', user1); + obj.set('field', 'field'); + obj.set('test', 'test'); + + await Parse.Object.saveAll([obj], { useMasterKey: true }); + + headers = { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Rest-API-Key': 'rest', + 'Content-Type': 'application/json', + 'X-Parse-Session-Token': user1.getSessionToken(), + }; + } + + beforeEach(async () => { + await initialize(); + }); + + it('should be enforced regardless of pointer-field being included in keys (select)', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': [], + }, + }); + + const query = new Parse.Query('AnObject'); + query.select('field', 'test'); + + const object = await query.get(obj.id); + expect(object.get('field')).toBe('field'); + expect(object.get('test')).toBe('test'); + done(); + }); + + it('should protect fields for query where pointer field is not included via keys (REST GET)', async done => { + const obj = new Parse.Object(className); + + obj.set('owner', user1); + obj.set('field', 'field'); + obj.set('test', 'test'); + + await Parse.Object.saveAll([obj], { useMasterKey: true }); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': ['test'], + }, + }); + + const { data: object } = await request({ + url: `${Parse.serverURL}/classes/${className}/${obj.id}`, + qs: { + keys: 'field,test', + }, + headers: headers, + }); + + expect(object.field).toBe( + 'field', + 'Should BE in response - not protected by "userField:owner"' + ); + expect(object.test).toBe( + undefined, + 'Should NOT be in response - protected by "userField:owner"' + ); + expect(object.owner).toBe( + undefined, + 'Should not be in response - not included in "keys"' + ); + done(); + }); + + it('should protect fields for query where pointer field is not included via keys (REST FIND)', async done => { + const obj = new Parse.Object(className); + + obj.set('owner', user1); + obj.set('field', 'field'); + obj.set('test', 'test'); + + await Parse.Object.saveAll([obj], { useMasterKey: true }); + + await obj.fetch(); + + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': ['test'], + }, + }); + + const { data } = await request({ + url: `${Parse.serverURL}/classes/${className}`, + qs: { + keys: 'field,test', + where: JSON.stringify({ objectId: obj.id }), + }, + headers, + }); + + const object = data.results[0]; + + expect(object.field).toBe( + 'field', + 'Should be in response - not protected by "userField:owner"' + ); + expect(object.test).toBe( + undefined, + 'Should not be in response - protected by "userField:owner"' + ); + expect(object.owner).toBe( + undefined, + 'Should not be in response - not included in "keys"' + ); + done(); + }); + + it('should protect fields for query where pointer field is in excludeKeys (REST GET)', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': ['test'], + }, + }); + + const { data: object } = await request({ + qs: { + excludeKeys: 'owner', + }, + headers, + url: `${Parse.serverURL}/classes/${className}/${obj.id}`, + }); + + expect(object.field).toBe( + 'field', + 'Should be in response - not protected by "userField:owner"' + ); + expect(object['test']).toBe( + undefined, + 'Should not be in response - protected by "userField:owner"' + ); + expect(object['owner']).toBe( + undefined, + 'Should not be in response - not included in "keys"' + ); + done(); + }); + + it('should protect fields for query where pointer field is in excludedKeys (REST FIND)', async done => { + await updateCLP({ + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': ['test'], + }, + get: { '*': true }, + find: { '*': true }, + }); + + const { data } = await request({ + qs: { + excludeKeys: 'owner', + where: JSON.stringify({ objectId: obj.id }), + }, + headers, + url: `${Parse.serverURL}/classes/${className}`, + }); + + const object = data.results[0]; + + expect(object.field).toBe( + 'field', + 'Should be in response - not protected by "userField:owner"' + ); + expect(object.test).toBe( + undefined, + 'Should not be in response - protected by "userField:owner"' + ); + expect(object.owner).toBe( + undefined, + 'Should not be in response - not included in "keys"' + ); + done(); + }); + + xit('todo: should be enforced regardless of pointer-field being excluded', async done => { + await updateCLP({ + get: { '*': true }, + find: { '*': true }, + protectedFields: { + '*': ['field', 'test'], + 'userField:owner': [], + }, + }); + + const query = new Parse.Query('AnObject'); + + /* TODO: this has some caching problems on JS-SDK (2.11.) side */ + // query.exclude('owner') + + const object = await query.get(obj.id); + expect(object.get('field')).toBe('field'); + expect(object.get('test')).toBe('test'); + expect(object.get('owner')).toBe(undefined); + done(); }); }); }); diff --git a/spec/RegexVulnerabilities.spec.js b/spec/RegexVulnerabilities.spec.js new file mode 100644 index 0000000000..58f5afac92 --- /dev/null +++ b/spec/RegexVulnerabilities.spec.js @@ -0,0 +1,198 @@ +const request = require('../lib/request'); + +const serverURL = 'http://localhost:8378/1'; +const headers = { + 'Content-Type': 'application/json', +}; +const keys = { + _ApplicationId: 'test', + _JavaScriptKey: 'test', +}; +const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, +}; +const appName = 'test'; +const publicServerURL = 'http://localhost:8378/1'; + +describe('Regex Vulnerabilities', function() { + beforeEach(async function() { + await reconfigureServer({ + verifyUserEmails: true, + emailAdapter, + appName, + publicServerURL, + }); + + const signUpResponse = await request({ + url: `${serverURL}/users`, + method: 'POST', + headers, + body: JSON.stringify({ + ...keys, + _method: 'POST', + username: 'someemail@somedomain.com', + password: 'somepassword', + email: 'someemail@somedomain.com', + }), + }); + this.objectId = signUpResponse.data.objectId; + this.sessionToken = signUpResponse.data.sessionToken; + this.partialSessionToken = this.sessionToken.slice(0, 3); + }); + + describe('on session token', function() { + it('should not work with regex', async function() { + try { + await request({ + url: `${serverURL}/users/me`, + method: 'POST', + headers, + body: JSON.stringify({ + ...keys, + _SessionToken: { + $regex: this.partialSessionToken, + }, + _method: 'GET', + }), + }); + fail('should not work'); + } catch (e) { + expect(e.data.code).toEqual(209); + expect(e.data.error).toEqual('Invalid session token'); + } + }); + + it('should work with plain token', async function() { + const meResponse = await request({ + url: `${serverURL}/users/me`, + method: 'POST', + headers, + body: JSON.stringify({ + ...keys, + _SessionToken: this.sessionToken, + _method: 'GET', + }), + }); + expect(meResponse.data.objectId).toEqual(this.objectId); + expect(meResponse.data.sessionToken).toEqual(this.sessionToken); + }); + }); + + describe('on verify e-mail', function() { + beforeEach(async function() { + const userQuery = new Parse.Query(Parse.User); + this.user = await userQuery.get(this.objectId, { useMasterKey: true }); + }); + + it('should not work with regex', async function() { + expect(this.user.get('emailVerified')).toEqual(false); + await request({ + url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token[$regex]=`, + method: 'GET', + }); + await this.user.fetch({ useMasterKey: true }); + expect(this.user.get('emailVerified')).toEqual(false); + }); + + it('should work with plain token', async function() { + expect(this.user.get('emailVerified')).toEqual(false); + // It should work + await request({ + url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${this.user.get( + '_email_verify_token' + )}`, + method: 'GET', + }); + await this.user.fetch({ useMasterKey: true }); + expect(this.user.get('emailVerified')).toEqual(true); + }); + }); + + describe('on password reset', function() { + beforeEach(async function() { + this.user = await Parse.User.logIn( + 'someemail@somedomain.com', + 'somepassword' + ); + }); + + it('should not work with regex', async function() { + expect(this.user.id).toEqual(this.objectId); + await request({ + url: `${serverURL}/requestPasswordReset`, + method: 'POST', + headers, + body: JSON.stringify({ + ...keys, + _method: 'POST', + email: 'someemail@somedomain.com', + }), + }); + await this.user.fetch({ useMasterKey: true }); + const passwordResetResponse = await request({ + url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token[$regex]=`, + method: 'GET', + }); + expect(passwordResetResponse.status).toEqual(302); + expect(passwordResetResponse.headers.location).toMatch( + `\\/invalid\\_link\\.html` + ); + await request({ + url: `${serverURL}/apps/test/request_password_reset`, + method: 'POST', + body: { + token: { $regex: '' }, + username: 'someemail@somedomain.com', + new_password: 'newpassword', + }, + }); + try { + await Parse.User.logIn('someemail@somedomain.com', 'newpassword'); + fail('should not work'); + } catch (e) { + expect(e.code).toEqual(101); + expect(e.message).toEqual('Invalid username/password.'); + } + }); + + it('should work with plain token', async function() { + expect(this.user.id).toEqual(this.objectId); + await request({ + url: `${serverURL}/requestPasswordReset`, + method: 'POST', + headers, + body: JSON.stringify({ + ...keys, + _method: 'POST', + email: 'someemail@somedomain.com', + }), + }); + await this.user.fetch({ useMasterKey: true }); + const token = this.user.get('_perishable_token'); + const passwordResetResponse = await request({ + url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token=${token}`, + method: 'GET', + }); + expect(passwordResetResponse.status).toEqual(302); + expect(passwordResetResponse.headers.location).toMatch( + `\\/choose\\_password\\?token\\=${token}\\&` + ); + await request({ + url: `${serverURL}/apps/test/request_password_reset`, + method: 'POST', + body: { + token, + username: 'someemail@somedomain.com', + new_password: 'newpassword', + }, + }); + const userAgain = await Parse.User.logIn( + 'someemail@somedomain.com', + 'newpassword' + ); + expect(userAgain.id).toEqual(this.objectId); + }); + }); +}); diff --git a/spec/dev.js b/spec/dev.js new file mode 100644 index 0000000000..31425dec40 --- /dev/null +++ b/spec/dev.js @@ -0,0 +1,98 @@ +const Config = require('../lib/Config'); +const Parse = require('parse/node'); + +const className = 'AnObject'; +const defaultRoleName = 'tester'; + +let schemaCache; + +module.exports = { + /* AnObject */ + className, + schemaCache, + + /** + * Creates and returns new user. + * + * This method helps to avoid 'User already exists' when re-running/debugging a single test. + * @param {string} username - username base, will be postfixed with current time in millis; + * @param {string} [password='password'] - optional, defaults to "password" if not set; + */ + createUser: async (username, password = 'password') => { + const user = new Parse.User({ + username: username + Date.now(), + password, + }); + await user.save(); + return user; + }, + + /** + * Logs the user in. + * + * If password not provided, default 'password' is used. + * @param {string} username - username base, will be postfixed with current time in millis; + * @param {string} [password='password'] - optional, defaults to "password" if not set; + */ + logIn: async (userObject, password) => { + return await Parse.User.logIn( + userObject.getUsername(), + password || 'password' + ); + }, + + /** + * Sets up Class-Level Permissions for 'AnObject' class. + * @param clp {ClassLevelPermissions} + */ + updateCLP: async (clp, targetClass = className) => { + const config = Config.get(Parse.applicationId); + const schemaController = await config.database.loadSchema(); + + await schemaController.updateClass(targetClass, {}, clp); + }, + + /** + * Creates and returns role. Adds user(s) if provided. + * + * This method helps to avoid errors when re-running/debugging a single test. + * + * @param {Parse.User|Parse.User[]} [users] - user or array of users to be related with this role; + * @param {string?} [roleName] - uses this name for role if provided. Generates from datetime if not set; + * @param {string?} [exactName] - sets exact name (no generated part added); + * @param {Parse.Role[]} [roles] - uses this name for role if provided. Generates from datetime if not set; + * @param {boolean} [read] - value for role's acl public read. Defaults to true; + * @param {boolean} [write] - value for role's acl public write. Defaults to true; + */ + createRole: async ({ + users = null, + exactName = defaultRoleName + Date.now(), + roleName = null, + roles = null, + read = true, + write = true, + }) => { + const acl = new Parse.ACL(); + acl.setPublicReadAccess(read); + acl.setPublicWriteAccess(write); + + const role = new Parse.Object('_Role'); + role.setACL(acl); + + // generate name based on roleName or use exactName (if botth not provided name is generated) + const name = roleName ? roleName + Date.now() : exactName; + role.set('name', name); + + if (roles) { + role.relation('roles').add(roles); + } + + if (users) { + role.relation('users').add(users); + } + + await role.save({ useMasterKey: true }); + + return role; + }, +}; diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index c547bf858a..661a19ee24 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -2861,6 +2861,35 @@ describe('schemas', () => { done(); }); + it('should be rejected if CLP pointerFields is not an array', async done => { + const config = Config.get(Parse.applicationId); + const schemaController = await config.database.loadSchema(); + + const operationKey = 'get'; + const entity = 'pointerFields'; + const value = {}; + + const schemaSetup = async () => + await schemaController.addClassIfNotExists( + 'AnObject', + {}, + { + [operationKey]: { + [entity]: value, + }, + } + ); + + await expectAsync(schemaSetup()).toBeRejectedWith( + new Parse.Error( + Parse.Error.INVALID_JSON, + `'${value}' is not a valid value for ${operationKey}[${entity}] - expected an array.` + ) + ); + + done(); + }); + describe('index management', () => { beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently()); it('cannot create index if field does not exist', done => { diff --git a/src/Config.js b/src/Config.js index 8d31d3d9ea..2077626ff8 100644 --- a/src/Config.js +++ b/src/Config.js @@ -34,8 +34,7 @@ export class Config { ); config.database = new DatabaseController( cacheInfo.databaseController.adapter, - schemaCache, - cacheInfo.skipMongoDBServer13732Workaround + schemaCache ); } else { config[key] = cacheInfo[key]; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 4b69d05e29..5c3b8ab342 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -69,73 +69,14 @@ const isSpecialQueryKey = key => { return specialQuerykeys.indexOf(key) >= 0; }; -const validateQuery = ( - query: any, - skipMongoDBServer13732Workaround: boolean -): void => { +const validateQuery = (query: any): void => { if (query.ACL) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } if (query.$or) { if (query.$or instanceof Array) { - query.$or.forEach(el => - validateQuery(el, skipMongoDBServer13732Workaround) - ); - - if (!skipMongoDBServer13732Workaround) { - /* In MongoDB 3.2 & 3.4, $or queries which are not alone at the top - * level of the query can not make efficient use of indexes due to a - * long standing bug known as SERVER-13732. - * - * This bug was fixed in MongoDB version 3.6. - * - * For versions pre-3.6, the below logic produces a substantial - * performance improvement inside the database by avoiding the bug. - * - * For versions 3.6 and above, there is no performance improvement and - * the logic is unnecessary. Some query patterns are even slowed by - * the below logic, due to the bug having been fixed and better - * query plans being chosen. - * - * When versions before 3.4 are no longer supported by this project, - * this logic, and the accompanying `skipMongoDBServer13732Workaround` - * flag, can be removed. - * - * This block restructures queries in which $or is not the sole top - * level element by moving all other top-level predicates inside every - * subdocument of the $or predicate, allowing MongoDB's query planner - * to make full use of the most relevant indexes. - * - * EG: {$or: [{a: 1}, {a: 2}], b: 2} - * Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]} - * - * The only exceptions are $near and $nearSphere operators, which are - * constrained to only 1 operator per query. As a result, these ops - * remain at the top level - * - * https://jira.mongodb.org/browse/SERVER-13732 - * https://github.com/parse-community/parse-server/issues/3767 - */ - Object.keys(query).forEach(key => { - const noCollisions = !query.$or.some(subq => - Object.prototype.hasOwnProperty.call(subq, key) - ); - let hasNears = false; - if (query[key] != null && typeof query[key] == 'object') { - hasNears = '$near' in query[key] || '$nearSphere' in query[key]; - } - if (key != '$or' && noCollisions && !hasNears) { - query.$or.forEach(subquery => { - subquery[key] = query[key]; - }); - delete query[key]; - } - }); - query.$or.forEach(el => - validateQuery(el, skipMongoDBServer13732Workaround) - ); - } + query.$or.forEach(validateQuery); } else { throw new Parse.Error( Parse.Error.INVALID_QUERY, @@ -146,9 +87,7 @@ const validateQuery = ( if (query.$and) { if (query.$and instanceof Array) { - query.$and.forEach(el => - validateQuery(el, skipMongoDBServer13732Workaround) - ); + query.$and.forEach(validateQuery); } else { throw new Parse.Error( Parse.Error.INVALID_QUERY, @@ -159,9 +98,7 @@ const validateQuery = ( if (query.$nor) { if (query.$nor instanceof Array && query.$nor.length > 0) { - query.$nor.forEach(el => - validateQuery(el, skipMongoDBServer13732Workaround) - ); + query.$nor.forEach(validateQuery); } else { throw new Parse.Error( Parse.Error.INVALID_QUERY, @@ -217,7 +154,7 @@ const filterSensitiveData = ( return { key: key.substring(10), value: perms.protectedFields[key] }; }); - const newProtectedFields: Array = []; + const newProtectedFields: Array[] = []; let overrideProtectedFields = false; // check if the object grants the current user access based on the extracted fields @@ -238,12 +175,28 @@ const filterSensitiveData = ( if (pointerPermIncludesUser) { overrideProtectedFields = true; - newProtectedFields.push(...pointerPerm.value); + newProtectedFields.push(pointerPerm.value); } }); - // if atleast one pointer-permission affected the current user override the protectedFields - if (overrideProtectedFields) protectedFields = newProtectedFields; + // if at least one pointer-permission affected the current user + // intersect vs protectedFields from previous stage (@see addProtectedFields) + // Sets theory (intersections): A x (B x C) == (A x B) x C + if (overrideProtectedFields && protectedFields) { + newProtectedFields.push(protectedFields); + } + // intersect all sets of protectedFields + newProtectedFields.forEach(fields => { + if (fields) { + // if there're no protctedFields by other criteria ( id / role / auth) + // then we must intersect each set (per userField) + if (!protectedFields) { + protectedFields = fields; + } else { + protectedFields = protectedFields.filter(v => fields.includes(v)); + } + } + }); } } @@ -251,9 +204,16 @@ const filterSensitiveData = ( /* special treat for the user class: don't filter protectedFields if currently loggedin user is the retrieved user */ - if (!(isUserClass && userId && object.objectId === userId)) + if (!(isUserClass && userId && object.objectId === userId)) { protectedFields && protectedFields.forEach(k => delete object[k]); + // fields not requested by client (excluded), + //but were needed to apply protecttedFields + perms.protectedFields && + perms.protectedFields.temporaryKeys && + perms.protectedFields.temporaryKeys.forEach(k => delete object[k]); + } + if (!isUserClass) { return object; } @@ -464,21 +424,15 @@ class DatabaseController { adapter: StorageAdapter; schemaCache: any; schemaPromise: ?Promise; - skipMongoDBServer13732Workaround: boolean; _transactionalSession: ?any; - constructor( - adapter: StorageAdapter, - schemaCache: any, - skipMongoDBServer13732Workaround: boolean - ) { + constructor(adapter: StorageAdapter, schemaCache: any) { this.adapter = adapter; this.schemaCache = schemaCache; // We don't want a mutable this.schema, because then you could have // one request that uses different schemas for different parts of // it. Instead, use loadSchema to get a schema. this.schemaPromise = null; - this.skipMongoDBServer13732Workaround = skipMongoDBServer13732Workaround; this._transactionalSession = null; } @@ -637,7 +591,7 @@ class DatabaseController { if (acl) { query = addWriteACL(query, acl); } - validateQuery(query, this.skipMongoDBServer13732Workaround); + validateQuery(query); return schemaController .getOneSchema(className, true) .catch(error => { @@ -916,7 +870,7 @@ class DatabaseController { if (acl) { query = addWriteACL(query, acl); } - validateQuery(query, this.skipMongoDBServer13732Workaround); + validateQuery(query); return schemaController .getOneSchema(className) .catch(error => { @@ -1416,7 +1370,8 @@ class DatabaseController { className, query, aclGroup, - auth + auth, + queryOptions ); } if (!query) { @@ -1436,7 +1391,7 @@ class DatabaseController { query = addReadACL(query, aclGroup); } } - validateQuery(query, this.skipMongoDBServer13732Workaround); + validateQuery(query); if (count) { if (!classExists) { return 0; @@ -1638,7 +1593,8 @@ class DatabaseController { className: string, query: any = {}, aclGroup: any[] = [], - auth: any = {} + auth: any = {}, + queryOptions: FullQueryOptions = {} ): null | string[] { const perms = schema.getClassLevelPermissions(className); if (!perms) return null; @@ -1648,14 +1604,85 @@ class DatabaseController { if (aclGroup.indexOf(query.objectId) > -1) return null; - // remove userField keys since they are filtered after querying - let protectedKeys = Object.keys(protectedFields).reduce((acc, val) => { - if (val.startsWith('userField:')) return acc; - return acc.concat(protectedFields[val]); + // for queries where "keys" are set and do not include all 'userField':{field}, + // we have to transparently include it, and then remove before returning to client + // Because if such key not projected the permission won't be enforced properly + // PS this is called when 'excludeKeys' already reduced to 'keys' + const preserveKeys = queryOptions.keys; + + // these are keys that need to be included only + // to be able to apply protectedFields by pointer + // and then unset before returning to client (later in filterSensitiveFields) + const serverOnlyKeys = []; + + const authenticated = auth.user; + + // map to allow check without array search + const roles = (auth.userRoles || []).reduce((acc, r) => { + acc[r] = protectedFields[r]; + return acc; + }, {}); + + // array of sets of protected fields. separate item for each applicable criteria + const protectedKeysSets = []; + + for (const key in protectedFields) { + // skip userFields + if (key.startsWith('userField:')) { + if (preserveKeys) { + const fieldName = key.substring(10); + if (!preserveKeys.includes(fieldName)) { + // 1. put it there temporarily + queryOptions.keys && queryOptions.keys.push(fieldName); + // 2. preserve it delete later + serverOnlyKeys.push(fieldName); + } + } + continue; + } + + // add public tier + if (key === '*') { + protectedKeysSets.push(protectedFields[key]); + continue; + } + + if (authenticated) { + if (key === 'authenticated') { + // for logged in users + protectedKeysSets.push(protectedFields[key]); + continue; + } + + if (roles[key] && key.startsWith('role:')) { + // add applicable roles + protectedKeysSets.push(roles[key]); + } + } + } + + // check if there's a rule for current user's id + if (authenticated) { + const userId = auth.user.id; + if (perms.protectedFields[userId]) { + protectedKeysSets.push(perms.protectedFields[userId]); + } + } + + // preserve fields to be removed before sending response to client + if (serverOnlyKeys.length > 0) { + perms.protectedFields.temporaryKeys = serverOnlyKeys; + } + + let protectedKeys = protectedKeysSets.reduce((acc, next) => { + if (next) { + acc.push(...next); + } + return acc; }, []); - [...(auth.userRoles || [])].forEach(role => { - const fields = protectedFields[role]; + // intersect all sets of protectedFields + protectedKeysSets.forEach(fields => { if (fields) { protectedKeys = protectedKeys.filter(v => fields.includes(v)); } @@ -1797,7 +1824,7 @@ class DatabaseController { ]); } - static _validateQuery: (any, boolean) => void; + static _validateQuery: any => void; } module.exports = DatabaseController; diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 8c799d0533..435a4b5570 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -175,32 +175,62 @@ const volatileClasses = Object.freeze([ // Anything that start with role const roleRegex = /^role:.*/; -// Anything that starts with userField -const pointerPermissionRegex = /^userField:.*/; +// Anything that starts with userField (allowed for protected fields only) +const protectedFieldsPointerRegex = /^userField:.*/; // * permission const publicRegex = /^\*$/; -const requireAuthenticationRegex = /^requiresAuthentication$/; +const authenticatedRegex = /^authenticated$/; -const pointerFieldsRegex = /^pointerFields$/; +const requiresAuthenticationRegex = /^requiresAuthentication$/; -const permissionKeyRegex = Object.freeze([ +const clpPointerRegex = /^pointerFields$/; + +// regex for validating entities in protectedFields object +const protectedFieldsRegex = Object.freeze([ + protectedFieldsPointerRegex, + publicRegex, + authenticatedRegex, roleRegex, - pointerPermissionRegex, +]); + +// clp regex +const clpFieldsRegex = Object.freeze([ + clpPointerRegex, publicRegex, - requireAuthenticationRegex, - pointerFieldsRegex, + requiresAuthenticationRegex, + roleRegex, ]); function validatePermissionKey(key, userIdRegExp) { let matchesSome = false; - for (const regEx of permissionKeyRegex) { + for (const regEx of clpFieldsRegex) { if (key.match(regEx) !== null) { matchesSome = true; break; } } + // userId depends on startup options so it's dynamic + const valid = matchesSome || key.match(userIdRegExp) !== null; + if (!valid) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + `'${key}' is not a valid key for class level permissions` + ); + } +} + +function validateProtectedFieldsKey(key, userIdRegExp) { + let matchesSome = false; + for (const regEx of protectedFieldsRegex) { + if (key.match(regEx) !== null) { + matchesSome = true; + break; + } + } + + // userId regex depends on launch options so it's dynamic const valid = matchesSome || key.match(userIdRegExp) !== null; if (!valid) { throw new Parse.Error( @@ -264,7 +294,7 @@ function validateCLP( if (operationKey === 'protectedFields') { for (const entity in operation) { // throws on unexpected key - validatePermissionKey(entity, userIdRegExp); + validateProtectedFieldsKey(entity, userIdRegExp); const protectedFields = operation[entity]; @@ -277,6 +307,13 @@ function validateCLP( // if the field is in form of array for (const field of protectedFields) { + // do not alloow to protect default fields + if (defaultColumns._Default[field]) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + `Default field '${field}' can not be protected` + ); + } // field should exist on collection if (!Object.prototype.hasOwnProperty.call(fields, field)) { throw new Parse.Error( @@ -301,6 +338,8 @@ function validateCLP( // throws on unexpected key validatePermissionKey(entity, userIdRegExp); + // entity can be either: + // "pointerFields": string[] if (entity === 'pointerFields') { const pointerFields = operation[entity]; @@ -311,13 +350,14 @@ function validateCLP( } else { throw new Parse.Error( Parse.Error.INVALID_JSON, - `'${pointerFields}' is not a valid value for protectedFields[${entity}] - expected an array.` + `'${pointerFields}' is not a valid value for ${operationKey}[${entity}] - expected an array.` ); } // proceed with next entity key continue; } + // or [entity]: boolean const permit = operation[entity]; if (permit !== true) { diff --git a/src/Controllers/index.js b/src/Controllers/index.js index 8ef767933f..d10ad8001c 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -171,7 +171,6 @@ export function getDatabaseController( const { databaseURI, databaseOptions, - skipMongoDBServer13732Workaround, collectionPrefix, schemaCacheTTL, enableSingleSchemaCache, @@ -195,8 +194,7 @@ export function getDatabaseController( } return new DatabaseController( databaseAdapter, - new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache), - skipMongoDBServer13732Workaround + new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache) ); } diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index d01f5eee0a..d596eae5b7 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -197,19 +197,60 @@ class ParseGraphQLSchema { if (this.graphQLCustomTypeDefs) { schemaDirectives.load(this); - this.graphQLSchema = mergeSchemas({ - schemas: [ - this.graphQLSchemaDirectivesDefinitions, - this.graphQLAutoSchema, - this.graphQLCustomTypeDefs, - ], - mergeDirectives: true, - }); + if (typeof this.graphQLCustomTypeDefs.getTypeMap === 'function') { + const customGraphQLSchemaTypeMap = this.graphQLCustomTypeDefs.getTypeMap(); + Object.values(customGraphQLSchemaTypeMap).forEach( + customGraphQLSchemaType => { + if ( + !customGraphQLSchemaType || + !customGraphQLSchemaType.name || + customGraphQLSchemaType.name.startsWith('__') + ) { + return; + } + const autoGraphQLSchemaType = this.graphQLAutoSchema.getType( + customGraphQLSchemaType.name + ); + if (autoGraphQLSchemaType) { + autoGraphQLSchemaType._fields = { + ...autoGraphQLSchemaType._fields, + ...customGraphQLSchemaType._fields, + }; + } + } + ); + this.graphQLSchema = mergeSchemas({ + schemas: [ + this.graphQLSchemaDirectivesDefinitions, + this.graphQLCustomTypeDefs, + this.graphQLAutoSchema, + ], + mergeDirectives: true, + }); + } else if (typeof this.graphQLCustomTypeDefs === 'function') { + this.graphQLSchema = await this.graphQLCustomTypeDefs({ + directivesDefinitionsSchema: this.graphQLSchemaDirectivesDefinitions, + autoSchema: this.graphQLAutoSchema, + mergeSchemas, + }); + } else { + this.graphQLSchema = mergeSchemas({ + schemas: [ + this.graphQLSchemaDirectivesDefinitions, + this.graphQLAutoSchema, + this.graphQLCustomTypeDefs, + ], + mergeDirectives: true, + }); + } const graphQLSchemaTypeMap = this.graphQLSchema.getTypeMap(); Object.keys(graphQLSchemaTypeMap).forEach(graphQLSchemaTypeName => { const graphQLSchemaType = graphQLSchemaTypeMap[graphQLSchemaTypeName]; - if (typeof graphQLSchemaType.getFields === 'function') { + if ( + typeof graphQLSchemaType.getFields === 'function' && + this.graphQLCustomTypeDefs.definitions + ) { const graphQLCustomTypeDef = this.graphQLCustomTypeDefs.definitions.find( definition => definition.name.value === graphQLSchemaTypeName ); diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 3e918c98b6..c1237deaea 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -3,6 +3,11 @@ import { offsetToCursor, cursorToOffset } from 'graphql-relay'; import rest from '../../rest'; import { transformQueryInputToParse } from '../transformers/query'; +const needToGetAllKeys = (fields, keys) => + keys + ? !!keys.split(',').find(keyName => !fields[keyName.split('.')[0]]) + : true; + const getObject = async ( className, objectId, @@ -12,10 +17,11 @@ const getObject = async ( includeReadPreference, config, auth, - info + info, + parseClass ) => { const options = {}; - if (keys) { + if (!needToGetAllKeys(parseClass.fields, keys)) { options.keys = keys; } if (include) { @@ -133,7 +139,14 @@ const findObjects = async ( // Silently replace the limit on the query with the max configured options.limit = config.maxLimit; } - if (keys) { + if ( + !needToGetAllKeys( + parseClasses.find( + ({ className: parseClassName }) => className === parseClassName + ).fields, + keys + ) + ) { options.keys = keys; } if (includeAll === true) { @@ -313,4 +326,4 @@ const calculateSkipAndLimit = ( }; }; -export { getObject, findObjects, calculateSkipAndLimit }; +export { getObject, findObjects, calculateSkipAndLimit, needToGetAllKeys }; diff --git a/src/GraphQL/loaders/defaultRelaySchema.js b/src/GraphQL/loaders/defaultRelaySchema.js index 3837bd5b9f..c3c2d9ccce 100644 --- a/src/GraphQL/loaders/defaultRelaySchema.js +++ b/src/GraphQL/loaders/defaultRelaySchema.js @@ -30,7 +30,10 @@ const load = parseGraphQLSchema => { undefined, config, auth, - info + info, + parseGraphQLSchema.parseClasses.find( + ({ className }) => type === className + ) )), }; } catch (e) { diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index 3ca333a0db..f41cccf5ed 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -112,8 +112,12 @@ const load = function( include, ['id', 'objectId', 'createdAt', 'updatedAt'] ); + const needToGetAllKeys = objectsQueries.needToGetAllKeys( + parseClass.fields, + keys + ); let optimizedObject = {}; - if (needGet) { + if (needGet && !needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, createdObject.objectId, @@ -123,7 +127,21 @@ const load = function( undefined, config, auth, - info + info, + parseClass + ); + } else if (needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + createdObject.objectId, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseClass ); } return { @@ -212,9 +230,12 @@ const load = function( include, ['id', 'objectId', 'updatedAt'] ); - + const needToGetAllKeys = objectsQueries.needToGetAllKeys( + parseClass.fields, + keys + ); let optimizedObject = {}; - if (needGet) { + if (needGet && !needToGetAllKeys) { optimizedObject = await objectsQueries.getObject( className, id, @@ -224,7 +245,21 @@ const load = function( undefined, config, auth, - info + info, + parseClass + ); + } else if (needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + id, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseClass ); } return { @@ -301,7 +336,8 @@ const load = function( undefined, config, auth, - info + info, + parseClass ); } await objectsMutations.deleteObject( diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index 80667836d0..cd1e71d922 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -14,7 +14,7 @@ const getParseClassQueryConfig = function( return (parseClassConfig && parseClassConfig.query) || {}; }; -const getQuery = async (className, _source, args, context, queryInfo) => { +const getQuery = async (parseClass, _source, args, context, queryInfo) => { let { id } = args; const { options } = args; const { readPreference, includeReadPreference } = options || {}; @@ -23,14 +23,14 @@ const getQuery = async (className, _source, args, context, queryInfo) => { const globalIdObject = fromGlobalId(id); - if (globalIdObject.type === className) { + if (globalIdObject.type === parseClass.className) { id = globalIdObject.id; } const { keys, include } = extractKeysAndInclude(selectedFields); return await objectsQueries.getObject( - className, + parseClass.className, id, keys, include, @@ -38,7 +38,8 @@ const getQuery = async (className, _source, args, context, queryInfo) => { includeReadPreference, config, auth, - info + info, + parseClass ); }; @@ -79,7 +80,7 @@ const load = function( ), async resolve(_source, args, context, queryInfo) { try { - return await getQuery(className, _source, args, context, queryInfo); + return await getQuery(parseClass, _source, args, context, queryInfo); } catch (e) { parseGraphQLSchema.handleError(e); } diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index 4909c3e992..b0272c4de6 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -436,7 +436,7 @@ const load = ( ); const parseOrder = order && order.join(','); - return await objectsQueries.findObjects( + return objectsQueries.findObjects( source[field].className, { $relatedTo: { diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index 6d5dbc601d..d2f3c8b7b5 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -122,7 +122,7 @@ const transformers = { parseGraphQLSchema, { config, auth, info } ) => { - if (Object.keys(value) === 0) + if (Object.keys(value).length === 0) throw new Parse.Error( Parse.Error.INVALID_POINTER, `You need to provide at least one operation on the relation mutation of field ${field}` @@ -203,7 +203,7 @@ const transformers = { parseGraphQLSchema, { config, auth, info } ) => { - if (Object.keys(value) > 1 || Object.keys(value) === 0) + if (Object.keys(value).length > 1 || Object.keys(value).length === 0) throw new Parse.Error( Parse.Error.INVALID_POINTER, `You need to provide link OR createLink on the pointer mutation of field ${field}` diff --git a/src/LiveQuery/ParseWebSocketServer.js b/src/LiveQuery/ParseWebSocketServer.js index 9e0d18d2a1..606056fc2e 100644 --- a/src/LiveQuery/ParseWebSocketServer.js +++ b/src/LiveQuery/ParseWebSocketServer.js @@ -13,6 +13,10 @@ export class ParseWebSocketServer { logger.info('Parse LiveQuery Server starts running'); }; wss.onConnection = ws => { + ws.on('error', error => { + logger.error(error.message); + logger.error(JSON.stringify(ws)); + }); onConnect(new ParseWebSocket(ws)); // Send ping to client periodically const pingIntervalId = setInterval(() => { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 4bded75ab9..725002034c 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -371,12 +371,6 @@ module.exports.ParseServerOptions = { help: 'Disables console output', action: parsers.booleanParser, }, - skipMongoDBServer13732Workaround: { - env: 'PARSE_SKIP_MONGODB_SERVER_13732_WORKAROUND', - help: 'Circumvent Parse workaround for historical MongoDB bug SERVER-13732', - action: parsers.booleanParser, - default: false, - }, startLiveQueryServer: { env: 'PARSE_SERVER_START_LIVE_QUERY_SERVER', help: 'Starts the liveQuery server', diff --git a/src/Options/docs.js b/src/Options/docs.js index 098fefb563..9e5b6ac8f8 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -67,7 +67,6 @@ * @property {String} serverURL URL to your parse server with http:// or https://. * @property {Number} sessionLength Session duration, in seconds, defaults to 1 year * @property {Boolean} silent Disables console output - * @property {Boolean} skipMongoDBServer13732Workaround Circumvent Parse workaround for historical MongoDB bug SERVER-13732 * @property {Boolean} startLiveQueryServer Starts the liveQuery server * @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields * @property {Boolean} verbose Set the logging to verbose diff --git a/src/Options/index.js b/src/Options/index.js index 43922ed36c..3aa5b7fc6b 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -64,10 +64,6 @@ export interface ParseServerOptions { databaseOptions: ?any; /* Adapter module for the database */ databaseAdapter: ?Adapter; - /* Circumvent Parse workaround for historical MongoDB bug SERVER-13732 - :ENV: PARSE_SKIP_MONGODB_SERVER_13732_WORKAROUND - :DEFAULT: false */ - skipMongoDBServer13732Workaround: ?boolean; /* Full path to your cloud code main.js */ cloud: ?string; /* A collection prefix for the classes diff --git a/src/ParseServer.js b/src/ParseServer.js index 5611c50491..1fd279b4c7 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -262,10 +262,12 @@ class ParseServer { if (options.mountGraphQL === true || options.mountPlayground === true) { let graphQLCustomTypeDefs = undefined; - if (options.graphQLSchema) { + if (typeof options.graphQLSchema === 'string') { graphQLCustomTypeDefs = parse( fs.readFileSync(options.graphQLSchema, 'utf8') ); + } else if (typeof options.graphQLSchema === 'object') { + graphQLCustomTypeDefs = options.graphQLSchema; } const parseGraphQLServer = new ParseGraphQLServer(this, { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 0cbc8d215d..d85101af6e 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -6,6 +6,7 @@ import Parse from 'parse/node'; const ALLOWED_GET_QUERY_KEYS = [ 'keys', 'include', + 'excludeKeys', 'readPreference', 'includeReadPreference', 'subqueryReadPreference', @@ -69,6 +70,9 @@ export class ClassesRouter extends PromiseRouter { if (body.include) { options.include = String(body.include); } + if (typeof body.excludeKeys == 'string') { + options.excludeKeys = body.excludeKeys; + } if (typeof body.readPreference === 'string') { options.readPreference = body.readPreference; } diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index f30c52892f..7453835473 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -11,7 +11,10 @@ const views = path.resolve(__dirname, '../../views'); export class PublicAPIRouter extends PromiseRouter { verifyEmail(req) { - const { token, username } = req.query; + const { username, token: rawToken } = req.query; + const token = + rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; + const appId = req.params.appId; const config = Config.get(appId); @@ -122,7 +125,9 @@ export class PublicAPIRouter extends PromiseRouter { return this.missingPublicServerURL(); } - const { username, token } = req.query; + const { username, token: rawToken } = req.query; + const token = + rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; if (!username || !token) { return this.invalidLink(req); @@ -158,7 +163,9 @@ export class PublicAPIRouter extends PromiseRouter { return this.missingPublicServerURL(); } - const { username, token, new_password } = req.body; + const { username, new_password, token: rawToken } = req.body; + const token = + rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; if ((!username || !token || !new_password) && req.xhr === false) { return this.invalidLink(req); diff --git a/src/middlewares.js b/src/middlewares.js index 75923e713f..f60cb53df6 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -105,6 +105,10 @@ export function handleParseHeaders(req, res, next) { } } + if (info.sessionToken && typeof info.sessionToken !== 'string') { + info.sessionToken = info.sessionToken.toString(); + } + if (info.clientVersion) { info.clientSDK = ClientSDK.fromString(info.clientVersion); }