diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a4c267503..dc27805e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: check-definitions: name: Check Definitions timeout-minutes: 5 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} diff --git a/.github/workflows/release-automated.yml b/.github/workflows/release-automated.yml index 9c4a6c6896..271d4abca3 100644 --- a/.github/workflows/release-automated.yml +++ b/.github/workflows/release-automated.yml @@ -42,7 +42,7 @@ jobs: env: REGISTRY: docker.io IMAGE_NAME: parseplatform/parse-server - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest permissions: contents: read packages: write @@ -86,7 +86,7 @@ jobs: docs: needs: release if: needs.release.outputs.current_tag != '' && github.ref == 'refs/heads/release' - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release-manual-docker.yml b/.github/workflows/release-manual-docker.yml index 3300f2a498..1d19189c3a 100644 --- a/.github/workflows/release-manual-docker.yml +++ b/.github/workflows/release-manual-docker.yml @@ -14,7 +14,7 @@ env: IMAGE_NAME: parseplatform/parse-server jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest permissions: contents: read packages: write diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 18eb32b574..a2b4c268d6 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,66 @@ +# [6.1.0-alpha.8](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.7...6.1.0-alpha.8) (2023-05-01) + + +### Features + +* Allow multiple origins for header `Access-Control-Allow-Origin` ([#8517](https://github.com/parse-community/parse-server/issues/8517)) ([4f15539](https://github.com/parse-community/parse-server/commit/4f15539ac244aa2d393ac5177f7604b43f69e271)) + +# [6.1.0-alpha.7](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.6...6.1.0-alpha.7) (2023-03-10) + + +### Bug Fixes + +* Rate limiting across multiple servers via Redis not working ([#8469](https://github.com/parse-community/parse-server/issues/8469)) ([d9e347d](https://github.com/parse-community/parse-server/commit/d9e347d7413f30f58ffbb8397fc8b5ae23be6ff0)) + +# [6.1.0-alpha.6](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.5...6.1.0-alpha.6) (2023-03-06) + + +### Features + +* Add rate limiting across multiple servers via Redis ([#8394](https://github.com/parse-community/parse-server/issues/8394)) ([34833e4](https://github.com/parse-community/parse-server/commit/34833e42eec08b812b733be78df0535ab0e096b6)) + +# [6.1.0-alpha.5](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.4...6.1.0-alpha.5) (2023-03-06) + + +### Bug Fixes + +* LiveQuery can return incorrectly formatted date ([#8456](https://github.com/parse-community/parse-server/issues/8456)) ([4ce135a](https://github.com/parse-community/parse-server/commit/4ce135a4fe930776044bc8fd786a4e17a0144e03)) + +# [6.1.0-alpha.4](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.3...6.1.0-alpha.4) (2023-03-06) + + +### Bug Fixes + +* Parameters missing in `afterFind` trigger of authentication adapters ([#8458](https://github.com/parse-community/parse-server/issues/8458)) ([ce34747](https://github.com/parse-community/parse-server/commit/ce34747e8af54cb0b6b975da38f779a5955d2d59)) + +# [6.1.0-alpha.3](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.2...6.1.0-alpha.3) (2023-03-06) + + +### Features + +* Add `afterFind` trigger to authentication adapters ([#8444](https://github.com/parse-community/parse-server/issues/8444)) ([c793bb8](https://github.com/parse-community/parse-server/commit/c793bb88e7485743c7ceb65fe419cde75833ff33)) + +# [6.1.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.1...6.1.0-alpha.2) (2023-03-05) + + +### Bug Fixes + +* Nested date is incorrectly decoded as empty object `{}` when fetching a Parse Object ([#8446](https://github.com/parse-community/parse-server/issues/8446)) ([22d2446](https://github.com/parse-community/parse-server/commit/22d2446dfea2bc339affc20535d181097e152acf)) + +# [6.1.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.0.0...6.1.0-alpha.1) (2023-03-03) + + +### Bug Fixes + +* Security upgrade jsonwebtoken to 9.0.0 ([#8420](https://github.com/parse-community/parse-server/issues/8420)) ([f5bfe45](https://github.com/parse-community/parse-server/commit/f5bfe4571e82b2b7440d41f3cff0d49937398164)) + +### Features + +* Add option `schemaCacheTtl` for schema cache pulling as alternative to `enableSchemaHooks` ([#8436](https://github.com/parse-community/parse-server/issues/8436)) ([b3b76de](https://github.com/parse-community/parse-server/commit/b3b76de71b1d4265689d052e7837c38ec1fa4323)) +* Add Parse Server option `resetPasswordSuccessOnInvalidEmail` to choose success or error response on password reset with invalid email ([#7551](https://github.com/parse-community/parse-server/issues/7551)) ([e5d610e](https://github.com/parse-community/parse-server/commit/e5d610e5e487ddab86409409ac3d7362aba8f59b)) +* Deprecate LiveQuery `fields` option in favor of `keys` for semantic consistency ([#8388](https://github.com/parse-community/parse-server/issues/8388)) ([a49e323](https://github.com/parse-community/parse-server/commit/a49e323d5ae640bff1c6603ec37fdaddb9328dd1)) +* Export `AuthAdapter` to make it available for extension with custom authentication adapters ([#8443](https://github.com/parse-community/parse-server/issues/8443)) ([40c1961](https://github.com/parse-community/parse-server/commit/40c196153b8efa12ae384c1c0092b2ed60a260d6)) + # [6.0.0-alpha.35](https://github.com/parse-community/parse-server/compare/6.0.0-alpha.34...6.0.0-alpha.35) (2023-02-27) diff --git a/jsdoc-conf.json b/jsdoc-conf.json index ad059acda1..efbaa0a37c 100644 --- a/jsdoc-conf.json +++ b/jsdoc-conf.json @@ -1,10 +1,16 @@ { "plugins": ["node_modules/jsdoc-babel", "plugins/markdown"], "babel": { - "plugins": ["@babel/plugin-transform-flow-strip-types"] + "plugins": ["@babel/plugin-transform-flow-strip-types"] }, "source": { - "include": ["./README.md", "./src/cloud-code", "./src/Options/docs.js", "./src/ParseServer.js", "./src/Adapters"], + "include": [ + "README.md", + "./src/cloud-code", + "./src/Options/docs.js", + "./src/ParseServer.js", + "./src/Adapters" + ], "excludePattern": "(^|\\/|\\\\)_" }, "templates": { @@ -17,7 +23,18 @@ "monospaceLinks": false }, "opts": { - "template": "node_modules/@parse/minami", - "recurse": true + "encoding": "utf8", + "readme": "./README.md", + "recurse": true, + "template": "./node_modules/clean-jsdoc-theme", + "theme_opts": { + "default_theme": "dark", + "title": "", + "create_style": "header, .sidebar-section-title, .sidebar-title { color: #139cee !important } .logo { margin-left : 40px; margin-right: 40px }" + } + }, + "markdown": { + "hardwrap": false, + "idInHeadings": true } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index fe5a6edfc0..ae752fdb36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-server", - "version": "6.1.0-beta.1", + "version": "6.1.0-alpha.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "6.1.0-beta.1", + "version": "6.1.0-alpha.8", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -41,9 +41,10 @@ "mustache": "4.2.0", "parse": "4.0.1", "path-to-regexp": "0.1.7", - "pg-monitor": "1.5.0", - "pg-promise": "10.12.1", + "pg-monitor": "2.0.0", + "pg-promise": "11.3.0", "pluralize": "8.0.0", + "rate-limit-redis": "3.0.1", "redis": "4.0.6", "semver": "7.3.8", "subscriptions-transport-ws": "0.11.0", @@ -64,7 +65,6 @@ "@babel/plugin-proposal-object-rest-spread": "7.10.0", "@babel/plugin-transform-flow-strip-types": "7.9.0", "@babel/preset-env": "7.10.0", - "@parse/minami": "1.0.0", "@saithodev/semantic-release-backmerge": "2.1.2", "@semantic-release/changelog": "5.0.1", "@semantic-release/commit-analyzer": "8.0.1", @@ -75,6 +75,7 @@ "all-node-versions": "11.3.0", "apollo-upload-client": "17.0.0", "bcrypt-nodejs": "0.0.3", + "clean-jsdoc-theme": "^4.2.7", "cross-env": "7.0.2", "deep-diff": "1.0.2", "eslint": "8.26.0", @@ -594,9 +595,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2287,6 +2288,18 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@napi-rs/triples": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.1.0.tgz", @@ -2745,12 +2758,6 @@ "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-1.2.2.tgz", "integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g==" }, - "node_modules/@parse/minami": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@parse/minami/-/minami-1.0.0.tgz", - "integrity": "sha512-Rw+p0WdOOypFPVJsmhyiI+Q056ZxdP2iAtObnU1DZrsvKZTf5x0B/0SjIt0hUgWp+COjqi/p17VdBU9IAD/NJg==", - "dev": true - }, "node_modules/@parse/node-apn": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", @@ -4060,11 +4067,11 @@ } }, "node_modules/assert-options": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.7.0.tgz", - "integrity": "sha512-7q9uNH/Dh8gFgpIIb9ja8PJEWA5AQy3xnBC8jtKs8K/gNVCr1K6kIvlm59HUyYgvM7oEDoLzGgPcGd9FqhtXEQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.0.tgz", + "integrity": "sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA==", "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" } }, "node_modules/assert-plus": { @@ -4686,6 +4693,16 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -4858,6 +4875,59 @@ "node": ">=0.10.0" } }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-jsdoc-theme": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/clean-jsdoc-theme/-/clean-jsdoc-theme-4.2.7.tgz", + "integrity": "sha512-yLLxwTFw7N9/toUCcFmKkwJmv4LO2pQWYNETcU3Vv+n2al2L2ohb7xWoXLjp5m3OljC9b1m+o4adzY0YH6Ep4A==", + "dev": true, + "dependencies": { + "@jsdoc/salty": "^0.2.4", + "fs-extra": "^10.1.0", + "html-minifier": "^4.0.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "nanoid": "^3.3.4", + "showdown": "^2.1.0" + }, + "peerDependencies": { + "jsdoc": ">=3.x <=4.x" + } + }, + "node_modules/clean-jsdoc-theme/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -8718,6 +8788,15 @@ "node": ">=8" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -8766,6 +8845,33 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "dev": true, + "dependencies": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -10120,6 +10226,15 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -10885,6 +11000,12 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -12227,6 +12348,15 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/node-abort-controller": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz", @@ -15901,6 +16031,15 @@ "node": ">= 4.0.0" } }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -16069,15 +16208,15 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz", + "integrity": "sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==", "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.5.0", "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", + "pg-protocol": "^1.6.0", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -16115,14 +16254,14 @@ } }, "node_modules/pg-monitor": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-monitor/-/pg-monitor-1.5.0.tgz", - "integrity": "sha512-Zg5RpoYaz0zyRwAQWKrRxUZgzZ+/r4McMP4vEvg+qE8765SHAB1wHZL58uAjocG4WSK/NLP/zZhUuoyurw4l6Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pg-monitor/-/pg-monitor-2.0.0.tgz", + "integrity": "sha512-UqjhroM701sRrJHhXeF1OwNBGxkN9R0YgkVU8A46wWn3RwK/K7QDylChMoDxo8TmGp86CBP4ZSf+RK9vD8XyVA==", "dependencies": { "cli-color": "2.0.3" }, "engines": { - "node": ">=7.6" + "node": ">=14" } }, "node_modules/pg-monitor/node_modules/cli-color": { @@ -16189,23 +16328,23 @@ } }, "node_modules/pg-promise": { - "version": "10.12.1", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.12.1.tgz", - "integrity": "sha512-SiJkBUDGq7PNfJFJbWferodsSH+vLrhte0Q0kVgQbwlNYeKmp9Hhkr+357+5DWEuBGOHhSu1UQffSSf5HVqRtA==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.3.0.tgz", + "integrity": "sha512-A2CYmax5gsqVAO2N0ET9oPRCPX3kpKymj9qLVK7+jszlJL6l8uJDq/DGqLpxNi5VHwK7Dmm2WNRdrwkh1xuaxQ==", "dependencies": { - "assert-options": "0.7.0", - "pg": "8.8.0", + "assert-options": "0.8.0", + "pg": "8.9.0", "pg-minify": "1.6.2", "spex": "3.2.0" }, "engines": { - "node": ">=12.0" + "node": ">=14.0" } }, "node_modules/pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" }, "node_modules/pg-types": { "version": "2.2.0", @@ -16746,6 +16885,17 @@ "node": ">= 0.6" } }, + "node_modules/rate-limit-redis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-3.0.1.tgz", + "integrity": "sha512-L6yhOUBrAZ8VEMX9DwlM3X6hfm8yq+gBO4LoOW7+JgmNq59zE7QmLz4v5VnwYPvLeSh/e7PDcrzUI3UumJw1iw==", + "engines": { + "node": ">= 14.5.0" + }, + "peerDependencies": { + "express-rate-limit": "^6" + } + }, "node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -17308,6 +17458,15 @@ "jsesc": "bin/jsesc" } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -18151,6 +18310,31 @@ "node": ">=8" } }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dev": true, + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -19610,7 +19794,6 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, - "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -19836,6 +20019,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -20810,9 +20999,9 @@ } }, "@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.10.0", @@ -22021,6 +22210,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, "@napi-rs/triples": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.1.0.tgz", @@ -22347,12 +22545,6 @@ "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-1.2.2.tgz", "integrity": "sha512-VUsVZXgt53FULqUd9xqGDW6RXes62qHXTNOeRSlS1MOemiCdtQOUGgLHgjdYQXnZ1hPLkxZKph96AluZUb953g==" }, - "@parse/minami": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@parse/minami/-/minami-1.0.0.tgz", - "integrity": "sha512-Rw+p0WdOOypFPVJsmhyiI+Q056ZxdP2iAtObnU1DZrsvKZTf5x0B/0SjIt0hUgWp+COjqi/p17VdBU9IAD/NJg==", - "dev": true - }, "@parse/node-apn": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", @@ -23386,9 +23578,9 @@ } }, "assert-options": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.7.0.tgz", - "integrity": "sha512-7q9uNH/Dh8gFgpIIb9ja8PJEWA5AQy3xnBC8jtKs8K/gNVCr1K6kIvlm59HUyYgvM7oEDoLzGgPcGd9FqhtXEQ==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.0.tgz", + "integrity": "sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA==" }, "assert-plus": { "version": "1.0.0", @@ -23865,6 +24057,16 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -23997,6 +24199,51 @@ "static-extend": "^0.1.1" } }, + "clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "clean-jsdoc-theme": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/clean-jsdoc-theme/-/clean-jsdoc-theme-4.2.7.tgz", + "integrity": "sha512-yLLxwTFw7N9/toUCcFmKkwJmv4LO2pQWYNETcU3Vv+n2al2L2ohb7xWoXLjp5m3OljC9b1m+o4adzY0YH6Ep4A==", + "dev": true, + "requires": { + "@jsdoc/salty": "^0.2.4", + "fs-extra": "^10.1.0", + "html-minifier": "^4.0.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "nanoid": "^3.3.4", + "showdown": "^2.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -26989,6 +27236,12 @@ } } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -27030,6 +27283,29 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "dev": true, + "requires": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -28074,6 +28350,15 @@ "graceful-fs": "^4.1.9" } }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -28693,6 +28978,12 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, "lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -29755,6 +30046,15 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, "node-abort-controller": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz", @@ -32407,6 +32707,15 @@ "wcwidth": "^1.0.1" } }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -32524,15 +32833,15 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz", + "integrity": "sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.5.0", "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", + "pg-protocol": "^1.6.0", "pg-types": "^2.1.0", "pgpass": "1.x" } @@ -32553,9 +32862,9 @@ "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==" }, "pg-monitor": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-monitor/-/pg-monitor-1.5.0.tgz", - "integrity": "sha512-Zg5RpoYaz0zyRwAQWKrRxUZgzZ+/r4McMP4vEvg+qE8765SHAB1wHZL58uAjocG4WSK/NLP/zZhUuoyurw4l6Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pg-monitor/-/pg-monitor-2.0.0.tgz", + "integrity": "sha512-UqjhroM701sRrJHhXeF1OwNBGxkN9R0YgkVU8A46wWn3RwK/K7QDylChMoDxo8TmGp86CBP4ZSf+RK9vD8XyVA==", "requires": { "cli-color": "2.0.3" }, @@ -32621,20 +32930,20 @@ "requires": {} }, "pg-promise": { - "version": "10.12.1", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.12.1.tgz", - "integrity": "sha512-SiJkBUDGq7PNfJFJbWferodsSH+vLrhte0Q0kVgQbwlNYeKmp9Hhkr+357+5DWEuBGOHhSu1UQffSSf5HVqRtA==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.3.0.tgz", + "integrity": "sha512-A2CYmax5gsqVAO2N0ET9oPRCPX3kpKymj9qLVK7+jszlJL6l8uJDq/DGqLpxNi5VHwK7Dmm2WNRdrwkh1xuaxQ==", "requires": { - "assert-options": "0.7.0", - "pg": "8.8.0", + "assert-options": "0.8.0", + "pg": "8.9.0", "pg-minify": "1.6.2", "spex": "3.2.0" } }, "pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" }, "pg-types": { "version": "2.2.0", @@ -33032,6 +33341,12 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "rate-limit-redis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-3.0.1.tgz", + "integrity": "sha512-L6yhOUBrAZ8VEMX9DwlM3X6hfm8yq+gBO4LoOW7+JgmNq59zE7QmLz4v5VnwYPvLeSh/e7PDcrzUI3UumJw1iw==", + "requires": {} + }, "raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -33491,6 +33806,12 @@ } } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -34130,6 +34451,23 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dev": true, + "requires": { + "commander": "^9.0.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + } + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -35288,8 +35626,7 @@ "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true + "dev": true }, "unbzip2-stream": { "version": "1.4.3", @@ -35451,6 +35788,12 @@ "picocolors": "^1.0.0" } }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 5cf9788023..cca14c5643 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "6.1.0-beta.1", + "version": "6.1.0-alpha.8", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -50,9 +50,10 @@ "mustache": "4.2.0", "parse": "4.0.1", "path-to-regexp": "0.1.7", - "pg-monitor": "1.5.0", - "pg-promise": "10.12.1", + "pg-monitor": "2.0.0", + "pg-promise": "11.3.0", "pluralize": "8.0.0", + "rate-limit-redis": "3.0.1", "redis": "4.0.6", "semver": "7.3.8", "subscriptions-transport-ws": "0.11.0", @@ -70,7 +71,6 @@ "@babel/plugin-proposal-object-rest-spread": "7.10.0", "@babel/plugin-transform-flow-strip-types": "7.9.0", "@babel/preset-env": "7.10.0", - "@parse/minami": "1.0.0", "@saithodev/semantic-release-backmerge": "2.1.2", "@semantic-release/changelog": "5.0.1", "@semantic-release/commit-analyzer": "8.0.1", @@ -81,6 +81,7 @@ "all-node-versions": "11.3.0", "apollo-upload-client": "17.0.0", "bcrypt-nodejs": "0.0.3", + "clean-jsdoc-theme": "4.2.7", "cross-env": "7.0.2", "deep-diff": "1.0.2", "eslint": "8.26.0", diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index 3a69217016..e0d33daa4b 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -161,6 +161,9 @@ function mapperFor(elt, t) { if (type == 'NumberOrBoolean') { return wrap(t.identifier('numberOrBooleanParser')); } + if (type === 'StringOrStringArray') { + return wrap(t.identifier('arrayParser')); + } return wrap(t.identifier('objectParser')); } } @@ -278,6 +281,9 @@ function inject(t, list) { const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name; type = `Adapter<${adapterType}>`; } + if (type === 'StringOrStringArray') { + type = 'String|String[]'; + } comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`; const obj = t.objectExpression(props); return t.objectProperty(t.stringLiteral(elt.name), obj); diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index 244349a89f..aaa172ea66 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -59,6 +59,19 @@ describe('Auth Adapter features', () => { validateLogin: () => Promise.resolve(), }; + const modernAdapter3 = { + validateAppId: () => Promise.resolve(), + validateSetUp: () => Promise.resolve(), + validateUpdate: () => Promise.resolve(), + validateLogin: () => Promise.resolve(), + validateOptions: () => Promise.resolve(), + afterFind() { + return { + foo: 'bar', + }; + }, + }; + const wrongAdapter = { validateAppId: () => Promise.resolve(), }; @@ -332,6 +345,30 @@ describe('Auth Adapter features', () => { expect(user.getSessionToken()).toBeDefined(); }); + it('should strip out authData if required', async () => { + const spy = spyOn(modernAdapter3, 'validateOptions').and.callThrough(); + const afterSpy = spyOn(modernAdapter3, 'afterFind').and.callThrough(); + await reconfigureServer({ auth: { modernAdapter3 } }); + const user = new Parse.User(); + await user.save({ authData: { modernAdapter3: { id: 'modernAdapter3Data' } } }); + await user.fetch({ sessionToken: user.getSessionToken() }); + const authData = user.get('authData').modernAdapter3; + expect(authData).toEqual({ foo: 'bar' }); + for (const call of afterSpy.calls.all()) { + const args = call.args[0]; + if (args.user) { + user._objCount = args.user._objCount; + break; + } + } + expect(afterSpy).toHaveBeenCalledWith( + { ip: '127.0.0.1', user, master: false }, + { id: 'modernAdapter3Data' }, + undefined + ); + expect(spy).toHaveBeenCalled(); + }); + it('should throw if no triggers found', async () => { await reconfigureServer({ auth: { wrongAdapter } }); const user = new Parse.User(); diff --git a/spec/Idempotency.spec.js b/spec/Idempotency.spec.js index a45d19343c..813923b1ff 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -45,10 +45,6 @@ describe('Idempotency', () => { }); }); - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 10000; - }); - // Tests it('should enforce idempotency for cloud code function', async () => { let counter = 0; diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 12bfc59bf7..636e7809f9 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -287,6 +287,35 @@ describe('middlewares', () => { expect(headers['Access-Control-Allow-Origin']).toEqual('https://parseplatform.org/'); }); + it('should support multiple origins if several are defined in allowOrigin as an array', () => { + AppCache.put(fakeReq.body._ApplicationId, { + allowOrigin: ['https://a.com', 'https://b.com', 'https://c.com'], + }); + const headers = {}; + const res = { + header: (key, value) => { + headers[key] = value; + }, + }; + const allowCrossDomain = middlewares.allowCrossDomain(fakeReq.body._ApplicationId); + // Test with the first domain + fakeReq.headers.origin = 'https://a.com'; + allowCrossDomain(fakeReq, res, () => {}); + expect(headers['Access-Control-Allow-Origin']).toEqual('https://a.com'); + // Test with the second domain + fakeReq.headers.origin = 'https://b.com'; + allowCrossDomain(fakeReq, res, () => {}); + expect(headers['Access-Control-Allow-Origin']).toEqual('https://b.com'); + // Test with the third domain + fakeReq.headers.origin = 'https://c.com'; + allowCrossDomain(fakeReq, res, () => {}); + expect(headers['Access-Control-Allow-Origin']).toEqual('https://c.com'); + // Test with an unauthorized domain + fakeReq.headers.origin = 'https://unauthorized.com'; + allowCrossDomain(fakeReq, res, () => {}); + expect(headers['Access-Control-Allow-Origin']).toEqual('https://a.com'); + }); + it('should use user provided on field userFromJWT', done => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index 58731d2432..1b5cc0c5e9 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -248,6 +248,10 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { expect(object.date[0] instanceof Date).toBeTrue(); expect(object.bar.date[0] instanceof Date).toBeTrue(); expect(object.foo.test.date[0] instanceof Date).toBeTrue(); + const obj = await new Parse.Query('MyClass').first({ useMasterKey: true }); + expect(obj.get('date')[0] instanceof Date).toBeTrue(); + expect(obj.get('bar').date[0] instanceof Date).toBeTrue(); + expect(obj.get('foo').test.date[0] instanceof Date).toBeTrue(); }); it('handles updating a single object with array, object date', done => { diff --git a/spec/ParseLiveQueryRedis.spec.js b/spec/ParseLiveQueryRedis.spec.js index 3187d23cc8..deb84bafb2 100644 --- a/spec/ParseLiveQueryRedis.spec.js +++ b/spec/ParseLiveQueryRedis.spec.js @@ -6,6 +6,7 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') { }); it('can connect', async () => { await reconfigureServer({ + appId: 'redis_live_query', startLiveQueryServer: true, liveQuery: { classNames: ['TestObject'], @@ -36,6 +37,7 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') { it('can call connect twice', async () => { const server = await reconfigureServer({ + appId: 'redis_live_query', startLiveQueryServer: true, liveQuery: { classNames: ['TestObject'], diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 5b4690ae85..50f29d0aab 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -115,6 +115,7 @@ describe('ParseLiveQueryServer', function () { }); describe_only_db('mongo')('initialization', () => { + beforeEach(() => reconfigureServer({ appId: 'mongo_init_test' })); it('can be initialized through ParseServer without liveQueryServerOptions', async () => { const parseServer = await ParseServer.startApp({ appId: 'hello', @@ -754,6 +755,49 @@ describe('ParseLiveQueryServer', function () { parseLiveQueryServer._onAfterSave(message); }); + it('sends correct object for dates', async () => { + jasmine.restoreLibrary('../lib/LiveQuery/QueryTools', 'matchesQuery'); + + const parseLiveQueryServer = new ParseLiveQueryServer({}); + + const date = new Date(); + const message = { + currentParseObject: { + date: { __type: 'Date', iso: date.toISOString() }, + __type: 'Object', + key: 'value', + className: testClassName, + }, + }; + // Add mock client + const clientId = 1; + const client = addMockClient(parseLiveQueryServer, clientId); + + const requestId2 = 2; + + await addMockSubscription(parseLiveQueryServer, clientId, requestId2); + + parseLiveQueryServer._matchesACL = function () { + return Promise.resolve(true); + }; + + parseLiveQueryServer._inflateParseObject(message); + parseLiveQueryServer._onAfterSave(message); + + // Make sure we send leave and enter command to client + await timeout(); + + expect(client.pushCreate).toHaveBeenCalledWith( + requestId2, + { + className: 'TestObject', + key: 'value', + date: { __type: 'Date', iso: date.toISOString() }, + }, + null + ); + }); + it('can handle object save command which does not match any subscription', async done => { const parseLiveQueryServer = new ParseLiveQueryServer({}); // Make mock request message @@ -1138,8 +1182,7 @@ describe('ParseLiveQueryServer', function () { expect(toSend.original).toBeUndefined(); expect(spy).toHaveBeenCalledWith({ usage: 'Subscribing using fields parameter', - solution: - `Subscribe using "keys" instead.`, + solution: `Subscribe using "keys" instead.`, }); }); @@ -1945,6 +1988,7 @@ describe('ParseLiveQueryServer', function () { } else { subscription.clientRequestIds = new Map([[clientId, [requestId]]]); } + subscription.query = query.where; return subscription; } diff --git a/spec/PostgresConfigParser.spec.js b/spec/PostgresConfigParser.spec.js index 412e7b20b9..f4efc42114 100644 --- a/spec/PostgresConfigParser.spec.js +++ b/spec/PostgresConfigParser.spec.js @@ -27,13 +27,13 @@ const baseURI = 'postgres://username:password@localhost:5432/db-name'; const testfile = fs.readFileSync('./Dockerfile').toString(); const dbOptionsTest = {}; dbOptionsTest[ - `${baseURI}?ssl=true&binary=true&application_name=app_name&fallback_application_name=f_app_name&poolSize=10` + `${baseURI}?ssl=true&binary=true&application_name=app_name&fallback_application_name=f_app_name&poolSize=12` ] = { ssl: true, binary: true, application_name: 'app_name', fallback_application_name: 'f_app_name', - poolSize: 10, + max: 12, }; dbOptionsTest[`${baseURI}?ssl=&binary=aa`] = { binary: false, @@ -83,6 +83,20 @@ describe('PostgresConfigParser.getDatabaseOptionsFromURI', () => { it('sets the poolSize to 10 if the it is not a number', () => { const result = parser.getDatabaseOptionsFromURI(`${baseURI}?poolSize=sdf`); - expect(result.poolSize).toEqual(10); + expect(result.max).toEqual(10); + }); + + it('sets the max to 10 if the it is not a number', () => { + const result = parser.getDatabaseOptionsFromURI(`${baseURI}?&max=sdf`); + + expect(result.poolSize).toBeUndefined(); + expect(result.max).toEqual(10); + }); + + it('max should take precedence over poolSize', () => { + const result = parser.getDatabaseOptionsFromURI(`${baseURI}?poolSize=20&max=12`); + + expect(result.poolSize).toBeUndefined(); + expect(result.max).toEqual(12); }); }); diff --git a/spec/RateLimit.spec.js b/spec/RateLimit.spec.js index 10bc127719..894c8fcf82 100644 --- a/spec/RateLimit.spec.js +++ b/spec/RateLimit.spec.js @@ -1,3 +1,4 @@ +const RedisCacheAdapter = require('../lib/Adapters/Cache/RedisCacheAdapter').default; describe('rate limit', () => { it('can limit cloud functions', async () => { Parse.Cloud.define('test', () => 'Abc'); @@ -388,4 +389,33 @@ describe('rate limit', () => { }) ).toBeRejectedWith(`Invalid rate limit option "path"`); }); + describe_only(() => { + return process.env.PARSE_SERVER_TEST_CACHE === 'redis'; + })('with RedisCache', function () { + it('does work with cache', async () => { + await reconfigureServer({ + rateLimit: [ + { + requestPath: '/classes/*', + requestTimeWindow: 10000, + requestCount: 1, + errorResponseMessage: 'Too many requests', + includeInternalRequests: true, + redisUrl: 'redis://localhost:6379', + }, + ], + }); + const obj = new Parse.Object('Test'); + await obj.save(); + await expectAsync(obj.save()).toBeRejectedWith( + new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests') + ); + const cache = new RedisCacheAdapter(); + await cache.connect(); + const value = await cache.get('rl:127.0.0.1'); + expect(value).toEqual(2); + const ttl = await cache.client.ttl('rl:127.0.0.1'); + expect(ttl).toEqual(10); + }); + }); }); diff --git a/spec/helper.js b/spec/helper.js index 445a137ac5..445de26509 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -198,6 +198,10 @@ beforeAll(async () => { Parse.serverURL = 'http://localhost:' + port + '/1'; }); +beforeEach(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 10000; +}); + afterEach(function (done) { const afterLogOut = async () => { if (Object.keys(openConnections).length > 0) { @@ -214,6 +218,7 @@ afterEach(function (done) { done(); }; Parse.Cloud._removeAllHooks(); + Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(); defaults.protectedFields = { _User: { '*': ['email'] } }; databaseAdapter .getAllClasses() diff --git a/spec/index.spec.js b/spec/index.spec.js index 64c4b54db7..08ef16a77b 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -558,7 +558,7 @@ describe('server', () => { }); it('can get starting state', async () => { - await reconfigureServer({ appId: 'test2', silent: false }); + await reconfigureServer({ appId: 'test2' }); const parseServer = new ParseServer.ParseServer({ ...defaultConfiguration, appId: 'test2', diff --git a/src/Adapters/Analytics/AnalyticsAdapter.js b/src/Adapters/Analytics/AnalyticsAdapter.js index 3fd50242d9..e3cced14f5 100644 --- a/src/Adapters/Analytics/AnalyticsAdapter.js +++ b/src/Adapters/Analytics/AnalyticsAdapter.js @@ -1,9 +1,7 @@ /*eslint no-unused-vars: "off"*/ -/** - * @module Adapters - */ /** * @interface AnalyticsAdapter + * @module Adapters */ export class AnalyticsAdapter { /** diff --git a/src/Adapters/Auth/AuthAdapter.js b/src/Adapters/Auth/AuthAdapter.js index 0e106014d5..5b18c75170 100644 --- a/src/Adapters/Auth/AuthAdapter.js +++ b/src/Adapters/Auth/AuthAdapter.js @@ -93,6 +93,24 @@ export class AuthAdapter { challenge(challengeData, authData, options, request) { return Promise.resolve({}); } + + /** + * Triggered when auth data is fetched + * @param {Object} authData authData + * @param {Object} options additional adapter options + * @returns {Promise} Any overrides required to authData + */ + afterFind(authData, options) { + return Promise.resolve({}); + } + + /** + * Triggered when the adapter is first attached to Parse Server + * @param {Object} options Adapter Options + */ + validateOptions(options) { + /* */ + } } export default AuthAdapter; diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 0338bfdcea..2defcb0dc0 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,5 +1,6 @@ import loadAdapter from '../AdapterLoader'; import Parse from 'parse/node'; +import AuthAdapter from './AuthAdapter'; const apple = require('./apple'); const gcenter = require('./gcenter'); @@ -153,28 +154,46 @@ function loadAuthAdapter(provider, authOptions) { return; } - const adapter = Object.assign({}, defaultAdapter); + const adapter = + defaultAdapter instanceof AuthAdapter ? defaultAdapter : Object.assign({}, defaultAdapter); + const keys = [ + 'validateAuthData', + 'validateAppId', + 'validateSetUp', + 'validateLogin', + 'validateUpdate', + 'challenge', + 'validateOptions', + 'policy', + 'afterFind', + ]; + const defaultAuthAdapter = new AuthAdapter(); + keys.forEach(key => { + const existing = adapter?.[key]; + if ( + existing && + typeof existing === 'function' && + existing.toString() === defaultAuthAdapter[key].toString() + ) { + adapter[key] = null; + } + }); const appIds = providerOptions ? providerOptions.appIds : undefined; // Try the configuration methods if (providerOptions) { const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); if (optionalAdapter) { - [ - 'validateAuthData', - 'validateAppId', - 'validateSetUp', - 'validateLogin', - 'validateUpdate', - 'challenge', - 'policy', - ].forEach(key => { + keys.forEach(key => { if (optionalAdapter[key]) { adapter[key] = optionalAdapter[key]; } }); } } + if (adapter.validateOptions) { + adapter.validateOptions(providerOptions); + } return { adapter, appIds, providerOptions }; } @@ -195,9 +214,40 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) { return { validator: authDataValidator(provider, adapter, appIds, providerOptions), adapter }; }; + const runAfterFind = async (req, authData) => { + if (!authData) { + return; + } + const adapters = Object.keys(authData); + await Promise.all( + adapters.map(async provider => { + const authAdapter = getValidatorForProvider(provider); + if (!authAdapter) { + return; + } + const { + adapter: { afterFind }, + providerOptions, + } = authAdapter; + if (afterFind && typeof afterFind === 'function') { + const requestObject = { + ip: req.config.ip, + user: req.auth.user, + master: req.auth.isMaster, + }; + const result = afterFind(requestObject, authData[provider], providerOptions); + if (result) { + authData[provider] = result; + } + } + }) + ); + }; + return Object.freeze({ getValidatorForProvider, setEnableAnonymousUsers, + runAfterFind, }); }; diff --git a/src/Adapters/Cache/CacheAdapter.js b/src/Adapters/Cache/CacheAdapter.js index 224d0c006e..7632797e55 100644 --- a/src/Adapters/Cache/CacheAdapter.js +++ b/src/Adapters/Cache/CacheAdapter.js @@ -1,9 +1,7 @@ /*eslint no-unused-vars: "off"*/ /** - * @module Adapters - */ -/** - * @interface CacheAdapter + * @interface + * @memberof module:Adapters */ export class CacheAdapter { /** diff --git a/src/Adapters/Email/MailAdapter.js b/src/Adapters/Email/MailAdapter.js index 791f9e6bd4..93069e2c27 100644 --- a/src/Adapters/Email/MailAdapter.js +++ b/src/Adapters/Email/MailAdapter.js @@ -1,9 +1,7 @@ /*eslint no-unused-vars: "off"*/ /** - * @module Adapters - */ -/** - * @interface MailAdapter + * @interface + * @memberof module:Adapters * Mail Adapter prototype * A MailAdapter should implement at least sendMail() */ diff --git a/src/Adapters/Files/FilesAdapter.js b/src/Adapters/Files/FilesAdapter.js index 68666b919a..afd06942e9 100644 --- a/src/Adapters/Files/FilesAdapter.js +++ b/src/Adapters/Files/FilesAdapter.js @@ -19,10 +19,8 @@ import type { Config } from '../../Config'; import Parse from 'parse/node'; /** - * @module Adapters - */ -/** - * @interface FilesAdapter + * @interface + * @memberof module:Adapters */ export class FilesAdapter { /** Responsible for storing the file in order to be retrieved later by its filename diff --git a/src/Adapters/Logger/LoggerAdapter.js b/src/Adapters/Logger/LoggerAdapter.js index 21df5ccd48..3853d5f480 100644 --- a/src/Adapters/Logger/LoggerAdapter.js +++ b/src/Adapters/Logger/LoggerAdapter.js @@ -1,9 +1,7 @@ /*eslint no-unused-vars: "off"*/ /** - * @module Adapters - */ -/** - * @interface LoggerAdapter + * @interface + * @memberof module:Adapters * Logger Adapter * Allows you to change the logger mechanism * Default is WinstonLoggerAdapter.js diff --git a/src/Adapters/PubSub/PubSubAdapter.js b/src/Adapters/PubSub/PubSubAdapter.js index 9e6b13dfc7..728dff90e8 100644 --- a/src/Adapters/PubSub/PubSubAdapter.js +++ b/src/Adapters/PubSub/PubSubAdapter.js @@ -1,9 +1,7 @@ /*eslint no-unused-vars: "off"*/ /** - * @module Adapters - */ -/** - * @interface PubSubAdapter + * @interface + * @memberof module:Adapters */ export class PubSubAdapter { /** diff --git a/src/Adapters/Push/PushAdapter.js b/src/Adapters/Push/PushAdapter.js index 191fa15b40..fb0adbf469 100644 --- a/src/Adapters/Push/PushAdapter.js +++ b/src/Adapters/Push/PushAdapter.js @@ -12,10 +12,8 @@ // android push and APNS for ios push. /** - * @module Adapters - */ -/** - * @interface PushAdapter + * @interface + * @memberof module:Adapters */ export class PushAdapter { /** diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index aabf744978..6f6811cec3 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -188,6 +188,16 @@ const transformInteriorValue = restValue => { // Handle atomic values var value = transformInteriorAtom(restValue); if (value !== CannotTransform) { + if (value && typeof value === 'object') { + if (value instanceof Date) { + return value; + } + if (value instanceof Array) { + value = value.map(transformInteriorValue); + } else { + value = mapValues(value, transformInteriorValue); + } + } return value; } @@ -1014,9 +1024,6 @@ function mapValues(object, iterator) { const result = {}; Object.keys(object).forEach(key => { result[key] = iterator(object[key]); - if (result[key] && JSON.stringify(result[key]).includes(`"__type"`)) { - result[key] = mapValues(object[key], iterator); - } }); return result; } diff --git a/src/Adapters/Storage/Postgres/PostgresConfigParser.js b/src/Adapters/Storage/Postgres/PostgresConfigParser.js index d86778cf20..64e4752913 100644 --- a/src/Adapters/Storage/Postgres/PostgresConfigParser.js +++ b/src/Adapters/Storage/Postgres/PostgresConfigParser.js @@ -58,7 +58,7 @@ function getDatabaseOptionsFromURI(uri) { databaseOptions.fallback_application_name = queryParams.fallback_application_name; if (queryParams.poolSize) { - databaseOptions.poolSize = parseInt(queryParams.poolSize) || 10; + databaseOptions.max = parseInt(queryParams.poolSize) || 10; } if (queryParams.max) { databaseOptions.max = parseInt(queryParams.max) || 10; diff --git a/src/Adapters/WebSocketServer/WSSAdapter.js b/src/Adapters/WebSocketServer/WSSAdapter.js index 007cacdf76..a810c03f9d 100644 --- a/src/Adapters/WebSocketServer/WSSAdapter.js +++ b/src/Adapters/WebSocketServer/WSSAdapter.js @@ -11,10 +11,8 @@ // Default is WSAdapter. The above functions will be binded. /** - * @module Adapters - */ -/** - * @interface WSSAdapter + * @interface + * @memberof module:Adapters */ export class WSSAdapter { /** diff --git a/src/Config.js b/src/Config.js index 2e7ef389c7..812d28c367 100644 --- a/src/Config.js +++ b/src/Config.js @@ -626,6 +626,16 @@ export class Config { return new Date(now.getTime() + this.sessionLength * 1000); } + unregisterRateLimiters() { + let i = this.rateLimits?.length; + while (i--) { + const limit = this.rateLimits[i]; + if (limit.cloud) { + this.rateLimits.splice(i, 1); + } + } + } + get invalidLinkURL() { return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`; } diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 934a556966..0b71265f33 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -24,6 +24,7 @@ import UserRouter from '../Routers/UsersRouter'; import DatabaseController from '../Controllers/DatabaseController'; import { isDeepStrictEqual } from 'util'; import Deprecator from '../Deprecator/Deprecator'; +import deepcopy from 'deepcopy'; class ParseLiveQueryServer { clients: Map; @@ -496,7 +497,7 @@ class ParseLiveQueryServer { if (!parseObject) { return false; } - return matchesQuery(parseObject, subscription.query); + return matchesQuery(deepcopy(parseObject), subscription.query); } async _clearCachedRoles(userId: string) { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index a25e69c70a..7987363ff2 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -81,7 +81,9 @@ module.exports.ParseServerOptions = { }, allowOrigin: { env: 'PARSE_SERVER_ALLOW_ORIGIN', - help: 'Sets the origin to Access-Control-Allow-Origin', + help: + 'Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins.', + action: parsers.arrayParser, }, analyticsAdapter: { env: 'PARSE_SERVER_ANALYTICS_ADAPTER', @@ -557,6 +559,11 @@ module.exports.RateLimitOptions = { action: parsers.booleanParser, default: false, }, + redisUrl: { + env: 'PARSE_SERVER_RATE_LIMIT_REDIS_URL', + help: + 'Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests.', + }, requestCount: { env: 'PARSE_SERVER_RATE_LIMIT_REQUEST_COUNT', help: diff --git a/src/Options/docs.js b/src/Options/docs.js index 0eb6488c74..b5a78aace1 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -16,7 +16,7 @@ * @property {Boolean} allowCustomObjectId Enable (or disable) custom objectId * @property {Boolean} allowExpiredAuthDataToken Allow a user to log in even if the 3rd party authentication token that was used to sign in to their account has expired. If this is set to `false`, then the token will be validated every time the user signs in to their account. This refers to the token that is stored in the `_User.authData` field. Defaults to `true`. * @property {String[]} allowHeaders Add headers to Access-Control-Allow-Headers - * @property {String} allowOrigin Sets the origin to Access-Control-Allow-Origin + * @property {String|String[]} allowOrigin Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins. * @property {Adapter} analyticsAdapter Adapter module for the analytics * @property {String} appId Your Parse Application ID * @property {String} appName Sets the app name @@ -104,6 +104,7 @@ * @property {String} errorResponseMessage The error message that should be returned in the body of the HTTP 429 response when the rate limit is hit. Default is `Too many requests.`. * @property {Boolean} includeInternalRequests Optional, if `true` the rate limit will also apply to requests that are made in by Cloud Code, default is `false`. Note that a public Cloud Code function that triggers internal requests may circumvent rate limiting and be vulnerable to attacks. * @property {Boolean} includeMasterKey Optional, if `true` the rate limit will also apply to requests using the `masterKey`, default is `false`. Note that a public Cloud Code function that triggers internal requests using the `masterKey` may circumvent rate limiting and be vulnerable to attacks. + * @property {String} redisUrl Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests. * @property {Number} requestCount The number of requests that can be made per IP address within the time window set in `requestTimeWindow` before the rate limit is applied. * @property {String[]} requestMethods Optional, the HTTP request methods to which the rate limit should be applied, default is all methods. * @property {String} requestPath The path of the API route to be rate limited. Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expression. See: https://expressjs.com/en/guide/routing.html diff --git a/src/Options/index.js b/src/Options/index.js index 778374e7e7..009b31a5d5 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -35,6 +35,7 @@ type Adapter = string | any | T; type NumberOrBoolean = number | boolean; type NumberOrString = number | string; type ProtectedFields = any; +type StringOrStringArray = string | string[]; type RequestKeywordDenylist = { key: string | any, value: any, @@ -61,8 +62,8 @@ export interface ParseServerOptions { appName: ?string; /* Add headers to Access-Control-Allow-Headers */ allowHeaders: ?(string[]); - /* Sets the origin to Access-Control-Allow-Origin */ - allowOrigin: ?string; + /* Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins. */ + allowOrigin: ?StringOrStringArray; /* Adapter module for the analytics */ analyticsAdapter: ?Adapter; /* Adapter module for the files sub-system */ @@ -320,6 +321,9 @@ export interface RateLimitOptions { /* Optional, if `true` the rate limit will also apply to requests that are made in by Cloud Code, default is `false`. Note that a public Cloud Code function that triggers internal requests may circumvent rate limiting and be vulnerable to attacks. :DEFAULT: false */ includeInternalRequests: ?boolean; + /* Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests. + */ + redisUrl: ?string; } export interface SecurityOptions { diff --git a/src/RestQuery.js b/src/RestQuery.js index f936a5a7a8..fe3617eb1b 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -223,6 +223,9 @@ RestQuery.prototype.execute = function (executeOptions) { .then(() => { return this.runAfterFindTrigger(); }) + .then(() => { + return this.handleAuthAdapters(); + }) .then(() => { return this.response; }); @@ -842,6 +845,20 @@ RestQuery.prototype.runAfterFindTrigger = function () { }); }; +RestQuery.prototype.handleAuthAdapters = async function () { + if (this.className !== '_User' || this.findOptions.explain) { + return; + } + await Promise.all( + this.response.results.map(result => + this.config.authDataManager.runAfterFind( + { config: this.config, auth: this.auth }, + result.authData + ) + ) + ); +}; + // Adds included values to the response. // Path is a list of field names. // Returns a promise for an augmented response. diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 4a72fdd73b..feca46e802 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -292,6 +292,7 @@ export class UsersRouter extends ClassesRouter { if (authDataResponse) { user.authDataResponse = authDataResponse; } + await req.config.authDataManager.runAfterFind(req, user.authData); return { response: user }; } diff --git a/src/Security/Check.js b/src/Security/Check.js index dc57d63088..c31480e73d 100644 --- a/src/Security/Check.js +++ b/src/Security/Check.js @@ -7,7 +7,7 @@ import { isFunction, isString } from 'lodash'; /** * A security check. - * @class Check + * @class */ class Check { /** diff --git a/src/Security/CheckGroup.js b/src/Security/CheckGroup.js index a9fde2905f..d8e3f8e08e 100644 --- a/src/Security/CheckGroup.js +++ b/src/Security/CheckGroup.js @@ -1,10 +1,7 @@ -/** - * @module SecurityCheck - */ - /** * A group of security checks. - * @interface CheckGroup + * @interface + * @memberof module:SecurityCheck */ class CheckGroup { constructor() { diff --git a/src/Security/CheckGroups/CheckGroupDatabase.js b/src/Security/CheckGroups/CheckGroupDatabase.js index f9b9340eb1..bc57fef8a3 100644 --- a/src/Security/CheckGroups/CheckGroupDatabase.js +++ b/src/Security/CheckGroups/CheckGroupDatabase.js @@ -1,7 +1,3 @@ -/** - * @module SecurityCheck - */ - import { Check } from '../Check'; import CheckGroup from '../CheckGroup'; import Config from '../../Config'; @@ -9,7 +5,8 @@ import Parse from 'parse/node'; /** * The security checks group for Parse Server configuration. - * Checks common Parse Server parameters such as access keys. + * Checks common Parse Server parameters such as access keys + * @memberof module:SecurityCheck */ class CheckGroupDatabase extends CheckGroup { setName() { diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index a9c6e671cc..b2b2376cb1 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -1,7 +1,3 @@ -/** - * @module SecurityCheck - */ - import { Check } from '../Check'; import CheckGroup from '../CheckGroup'; import Config from '../../Config'; @@ -10,6 +6,7 @@ import Parse from 'parse/node'; /** * The security checks group for Parse Server configuration. * Checks common Parse Server parameters such as access keys. + * @memberof module:SecurityCheck */ class CheckGroupServerConfig extends CheckGroup { setName() { diff --git a/src/Security/CheckGroups/CheckGroups.js b/src/Security/CheckGroups/CheckGroups.js index d1c4f03d8c..36f90e019c 100644 --- a/src/Security/CheckGroups/CheckGroups.js +++ b/src/Security/CheckGroups/CheckGroups.js @@ -1,5 +1,5 @@ /** - * @module SecurityCheck + * @memberof module:SecurityCheck */ /** diff --git a/src/Security/CheckRunner.js b/src/Security/CheckRunner.js index a662ffbad4..4be1c3acbf 100644 --- a/src/Security/CheckRunner.js +++ b/src/Security/CheckRunner.js @@ -1,7 +1,3 @@ -/** - * @module SecurityCheck - */ - import Utils from '../Utils'; import { CheckState } from './Check'; import * as CheckGroups from './CheckGroups/CheckGroups'; @@ -10,6 +6,7 @@ import { isArray, isBoolean } from 'lodash'; /** * The security check runner. + * @memberof module:SecurityCheck */ class CheckRunner { /** diff --git a/src/TestUtils.js b/src/TestUtils.js index 7772bcd65b..3d5133556d 100644 --- a/src/TestUtils.js +++ b/src/TestUtils.js @@ -1,4 +1,5 @@ import AppCache from './cache'; +import SchemaCache from './Adapters/Cache/SchemaCache'; /** * Destroys all data in the database @@ -11,11 +12,17 @@ export function destroyAllDataPermanently(fast) { return Promise.all( Object.keys(AppCache.cache).map(appId => { const app = AppCache.get(appId); + const deletePromises = []; + if (app.cacheAdapter) { + deletePromises.push(app.cacheAdapter.clear()); + } if (app.databaseController) { - return app.databaseController.deleteEverything(fast); - } else { - return Promise.resolve(); + deletePromises.push(app.databaseController.deleteEverything(fast)); + } else if (app.databaseAdapter) { + SchemaCache.clear(); + deletePromises.push(app.databaseAdapter.deleteAllClasses(fast)); } + return Promise.all(deletePromises); }) ); } diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 75faf44f3d..5540e8d719 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -128,7 +128,8 @@ ParseCloud.define = function (functionName, handler, validationHandler) { if (validationHandler && validationHandler.rateLimit) { addRateLimit( { requestPath: `/functions/${functionName}`, ...validationHandler.rateLimit }, - Parse.applicationId + Parse.applicationId, + true ); } }; @@ -191,7 +192,8 @@ ParseCloud.beforeSave = function (parseClass, handler, validationHandler) { requestMethods: ['POST', 'PUT'], ...validationHandler.rateLimit, }, - Parse.applicationId + Parse.applicationId, + true ); } }; @@ -237,7 +239,8 @@ ParseCloud.beforeDelete = function (parseClass, handler, validationHandler) { requestMethods: 'DELETE', ...validationHandler.rateLimit, }, - Parse.applicationId + Parse.applicationId, + true ); } }; @@ -278,7 +281,8 @@ ParseCloud.beforeLogin = function (handler, validationHandler) { if (validationHandler && validationHandler.rateLimit) { addRateLimit( { requestPath: `/login`, requestMethods: 'POST', ...validationHandler.rateLimit }, - Parse.applicationId + Parse.applicationId, + true ); } }; @@ -456,7 +460,8 @@ ParseCloud.beforeFind = function (parseClass, handler, validationHandler) { requestMethods: 'GET', ...validationHandler.rateLimit, }, - Parse.applicationId + Parse.applicationId, + true ); } }; @@ -761,6 +766,8 @@ ParseCloud.afterLiveQueryEvent = function (parseClass, handler, validationHandle ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); + const config = Config.get(Parse.applicationId); + config?.unregisterRateLimiters(); }; ParseCloud.useMasterKey = () => { diff --git a/src/index.js b/src/index.js index 684443ce5b..dcfe9b4c7e 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'; import LRUCacheAdapter from './Adapters/Cache/LRUCache.js'; import * as TestUtils from './TestUtils'; import * as SchemaMigrations from './SchemaMigrations/Migrations'; +import AuthAdapter from './Adapters/Auth/AuthAdapter'; import { useExternal } from './deprecated'; import { getLogger } from './logger'; @@ -43,4 +44,5 @@ export { ParseGraphQLServer, _ParseServer as ParseServer, SchemaMigrations, + AuthAdapter, }; diff --git a/src/middlewares.js b/src/middlewares.js index 788c9a3f62..2e450f3e03 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -11,6 +11,8 @@ import rateLimit from 'express-rate-limit'; import { RateLimitOptions } from './Options/Definitions'; import pathToRegexp from 'path-to-regexp'; import ipRangeCheck from 'ip-range-check'; +import RedisStore from 'rate-limit-redis'; +import { createClient } from 'redis'; export const DEFAULT_ALLOWED_HEADERS = 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control'; @@ -382,8 +384,13 @@ export function allowCrossDomain(appId) { if (config && config.allowHeaders) { allowHeaders += `, ${config.allowHeaders.join(', ')}`; } - const allowOrigin = (config && config.allowOrigin) || '*'; - res.header('Access-Control-Allow-Origin', allowOrigin); + + const baseOrigins = + typeof config?.allowOrigin === 'string' ? [config.allowOrigin] : config?.allowOrigin ?? ['*']; + const requestOrigin = req.headers.origin; + const allowOrigins = + requestOrigin && baseOrigins.includes(requestOrigin) ? requestOrigin : baseOrigins[0]; + res.header('Access-Control-Allow-Origin', allowOrigins); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', allowHeaders); res.header('Access-Control-Expose-Headers', 'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id'); @@ -464,7 +471,7 @@ export function promiseEnforceMasterKeyAccess(request) { return Promise.resolve(); } -export const addRateLimit = (route, config) => { +export const addRateLimit = (route, config, cloud) => { if (typeof config === 'string') { config = Config.get(config); } @@ -476,6 +483,35 @@ export const addRateLimit = (route, config) => { if (!config.rateLimits) { config.rateLimits = []; } + const redisStore = { + connectionPromise: Promise.resolve(), + store: null, + connected: false, + }; + if (route.redisUrl) { + const client = createClient({ + url: route.redisUrl, + }); + redisStore.connectionPromise = async () => { + if (redisStore.connected) { + return; + } + try { + await client.connect(); + redisStore.connected = true; + } catch (e) { + const log = config?.loggerController || defaultLogger; + log.error(`Could not connect to redisURL in rate limit: ${e}`); + } + }; + redisStore.connectionPromise(); + redisStore.store = new RedisStore({ + sendCommand: async (...args) => { + await redisStore.connectionPromise(); + return client.sendCommand(args); + }, + }); + } config.rateLimits.push({ path: pathToRegexp(route.requestPath), handler: rateLimit({ @@ -512,7 +548,9 @@ export const addRateLimit = (route, config) => { keyGenerator: request => { return request.config.ip; }, + store: redisStore.store, }), + cloud, }); Config.put(config); };