mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
39
Commits
9.10.0-alpha.8
...
alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
315e157637 | ||
|
|
64d58ff726 | ||
|
|
9e4e5dbbc9 | ||
|
|
472136c5ca | ||
|
|
90c277894f | ||
|
|
99a0471aaf | ||
|
|
997ee152c3 | ||
|
|
81bdeb8697 | ||
|
|
5838c07acc | ||
|
|
6f3e07ca80 | ||
|
|
b9912b0bb5 | ||
|
|
154e1d48bd | ||
|
|
34c8f759c9 | ||
|
|
b45f7ea3bf | ||
|
|
71e65572cb | ||
|
|
0ebd93569e | ||
|
|
c6fd388e27 | ||
|
|
629426f00d | ||
|
|
eab2e97482 | ||
|
|
42ae75f001 | ||
|
|
0df8779c2e | ||
|
|
d76845f058 | ||
|
|
6e87eb2544 | ||
|
|
d577327bff | ||
|
|
7d4d135d3f | ||
|
|
a7e792ac19 | ||
|
|
a2ea125678 | ||
|
|
538b1d7088 | ||
|
|
1b487e4c3f | ||
|
|
3f4d0d7c3c | ||
|
|
a972046b2e | ||
|
|
b28dba8812 | ||
|
|
0d260ff1ed | ||
|
|
504f919415 | ||
|
|
5c25152740 | ||
|
|
2275feed3e | ||
|
|
b5ca12fa5e | ||
|
|
fb1d6fc186 | ||
|
|
534a6b92d0 |
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
branches: [release, alpha, beta, next-major, 'release-[0-9]+.x.x']
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- '**'
|
||||
paths-ignore:
|
||||
@@ -15,6 +16,7 @@ permissions:
|
||||
jobs:
|
||||
check-code-analysis:
|
||||
name: Code Analysis
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -36,6 +38,7 @@ jobs:
|
||||
uses: github/codeql-action/analyze@v2
|
||||
check-ci:
|
||||
name: Node Engine Check
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -52,6 +55,7 @@ jobs:
|
||||
run: npm run ci:checkNodeEngine
|
||||
check-lint:
|
||||
name: Lint
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -72,6 +76,7 @@ jobs:
|
||||
- run: npm run lint
|
||||
check-definitions:
|
||||
name: Check Definitions
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -93,6 +98,7 @@ jobs:
|
||||
run: npm run ci:definitionsCheck
|
||||
check-circular:
|
||||
name: Circular Dependencies
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -113,6 +119,7 @@ jobs:
|
||||
- run: npm run madge:circular
|
||||
check-docs:
|
||||
name: Docs
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -136,6 +143,7 @@ jobs:
|
||||
run: npm run docs
|
||||
check-docker:
|
||||
name: Docker Build
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -153,6 +161,7 @@ jobs:
|
||||
platforms: linux/amd64, linux/arm64/v8
|
||||
check-lock-file-version:
|
||||
name: NPM Lock File Version
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -166,6 +175,7 @@ jobs:
|
||||
fi
|
||||
check-types:
|
||||
name: Check Types
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -206,6 +216,7 @@ jobs:
|
||||
NODE_VERSION: 22.12.0
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
@@ -260,6 +271,7 @@ jobs:
|
||||
NODE_VERSION: 24.11.0
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"text-summary"
|
||||
],
|
||||
"exclude": [
|
||||
"**/spec/**"
|
||||
"**/spec/**",
|
||||
"resources/**"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -162,9 +162,7 @@ cat > ./package.json << EOF
|
||||
"scripts": {
|
||||
"start": "parse-server config.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"parse-server": "^3.9.0"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
EOF
|
||||
echo "${CHECK} Created package.json"
|
||||
@@ -195,7 +193,7 @@ fi
|
||||
|
||||
echo "\n${CHECK} running npm install\n"
|
||||
|
||||
npm install -s
|
||||
npm install parse-server -s
|
||||
|
||||
CURL_CMD=$(cat << EOF
|
||||
curl -X POST -H 'X-Parse-Application-Id: ${APP_ID}' \\
|
||||
|
||||
@@ -1,3 +1,45 @@
|
||||
## [9.10.1-alpha.6](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.5...9.10.1-alpha.6) (2026-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `Parse.Query.explain` runs afterFind trigger on query plan results ([#10536](https://github.com/parse-community/parse-server/issues/10536)) ([64d58ff](https://github.com/parse-community/parse-server/commit/64d58ff726b42b45e107e27bc1f3b50be82b3189))
|
||||
|
||||
## [9.10.1-alpha.5](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.4...9.10.1-alpha.5) (2026-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Server crash from unhandled promise rejection when multiple Cloud Code validator fields fail ([#10540](https://github.com/parse-community/parse-server/issues/10540)) ([90c2778](https://github.com/parse-community/parse-server/commit/90c277894f31be8e64a4b049ad503621cf5f2285))
|
||||
|
||||
## [9.10.1-alpha.4](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.3...9.10.1-alpha.4) (2026-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Install the latest Parse Server version in bootstrap.sh ([#10556](https://github.com/parse-community/parse-server/issues/10556)) ([997ee15](https://github.com/parse-community/parse-server/commit/997ee152c358524bd2b0dded490255742b61a1ba))
|
||||
|
||||
## [9.10.1-alpha.3](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.2...9.10.1-alpha.3) (2026-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Bump ws from 8.20.0 to 8.21.0 ([#10576](https://github.com/parse-community/parse-server/issues/10576)) ([629426f](https://github.com/parse-community/parse-server/commit/629426f00d619ca624fc2d7e64d98f406ce20acc))
|
||||
|
||||
## [9.10.1-alpha.2](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.1...9.10.1-alpha.2) (2026-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Creating a session can delete another user's session ([#10582](https://github.com/parse-community/parse-server/issues/10582)) ([0df8779](https://github.com/parse-community/parse-server/commit/0df8779c2ecccb055e615d42ee85ae2bce491670))
|
||||
|
||||
## [9.10.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.10.0...9.10.1-alpha.1) (2026-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Bump follow-redirects from 1.15.11 to 1.16.0 ([#10577](https://github.com/parse-community/parse-server/issues/10577)) ([d577327](https://github.com/parse-community/parse-server/commit/d577327bff073b0436a158e9694feed8eed2f8e7))
|
||||
|
||||
# [9.10.0-alpha.8](https://github.com/parse-community/parse-server/compare/9.10.0-alpha.7...9.10.0-alpha.8) (2026-07-13)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
# [9.10.0](https://github.com/parse-community/parse-server/compare/9.9.0...9.10.0) (2026-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Cloud Code beforeFind trigger context is not isolated from prototype pollution ([#10570](https://github.com/parse-community/parse-server/issues/10570)) ([bea001e](https://github.com/parse-community/parse-server/commit/bea001e7ef9cf991e99fe1eb1e8e25ef71c530f7))
|
||||
* Cloud Function multipart requests bypass the maxUploadSize limit ([#10498](https://github.com/parse-community/parse-server/issues/10498)) ([f12e1c3](https://github.com/parse-community/parse-server/commit/f12e1c3e31fb211bb7fe106a9a295ac7d0dd4ea7))
|
||||
* Denial of service via exponential-time processing of deeply nested query operators ([GHSA-cgxm-vr2f-6fj8](https://github.com/parse-community/parse-server/security/advisories/GHSA-cgxm-vr2f-6fj8)) ([#10511](https://github.com/parse-community/parse-server/issues/10511)) ([1103c7a](https://github.com/parse-community/parse-server/commit/1103c7a890e0455ba3dccd4bc5db17efe1789c9a))
|
||||
* Endpoints `/login` and `/verifyPassword` disclose MFA secrets and protected fields when `_User` get is denied ([GHSA-75v4-m273-5j49](https://github.com/parse-community/parse-server/security/advisories/GHSA-75v4-m273-5j49)) ([#10492](https://github.com/parse-community/parse-server/issues/10492)) ([83e90ed](https://github.com/parse-community/parse-server/commit/83e90edbe4224c81172a20e40fa986662c9394ca))
|
||||
* GeoPoint distance queries fail with an internal server error on MongoDB 8.3 and later ([#10572](https://github.com/parse-community/parse-server/issues/10572)) ([b706c22](https://github.com/parse-community/parse-server/commit/b706c22cd9e47d91ec00d3f6050d161fd721e2e1))
|
||||
* GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers ([GHSA-8cph-rgr4-g5vj](https://github.com/parse-community/parse-server/security/advisories/GHSA-8cph-rgr4-g5vj)) ([#10467](https://github.com/parse-community/parse-server/issues/10467)) ([155123a](https://github.com/parse-community/parse-server/commit/155123ade9bc88cdf4807cf267ea1196f9274773))
|
||||
* GraphQL error messages disclose pointer and relation target class names when public introspection is disabled ([GHSA-r2g6-4f6j-f6rf](https://github.com/parse-community/parse-server/security/advisories/GHSA-r2g6-4f6j-f6rf)) ([#10568](https://github.com/parse-community/parse-server/issues/10568)) ([cb9b542](https://github.com/parse-community/parse-server/commit/cb9b54264d4dae4c0f9306924c34d805130b600e))
|
||||
* GraphQL error messages disclose required input field names when public introspection is disabled ([GHSA-2fgh-8j2g-w354](https://github.com/parse-community/parse-server/security/advisories/GHSA-2fgh-8j2g-w354)) ([#10566](https://github.com/parse-community/parse-server/issues/10566)) ([d96c945](https://github.com/parse-community/parse-server/commit/d96c945b6ddee652ecf27b240292e569367be550)), closes [GHSA-2f#8j2g-w354](https://github.com/GHSA-2f/issues/8j2g-w354) [/github.com/parse-community/parse-server/security/advisories/GHSA-2f#8j2g-w354](https://github.com//github.com/parse-community/parse-server/security/advisories/GHSA-2f/issues/8j2g-w354)
|
||||
* GraphQL variable-coercion suggestions disclose schema to unauthenticated callers ([GHSA-9g8f-h8f3-hjcm](https://github.com/parse-community/parse-server/security/advisories/GHSA-9g8f-h8f3-hjcm)) ([#10563](https://github.com/parse-community/parse-server/issues/10563)) ([2625489](https://github.com/parse-community/parse-server/commit/2625489a27c07aed1283c5dc9c5e8f8f53a9e44c))
|
||||
* LiveQuery discloses object data to a subscriber across an ACL read-access change ([GHSA-97pr-9hgg-3p8r](https://github.com/parse-community/parse-server/security/advisories/GHSA-97pr-9hgg-3p8r)) ([#10515](https://github.com/parse-community/parse-server/issues/10515)) ([e9c85df](https://github.com/parse-community/parse-server/commit/e9c85dfe40a866a55ebae3b6ae56285ac0a22e64))
|
||||
* LiveQuery subscriptions leak when a client reuses a subscribe requestId ([#10499](https://github.com/parse-community/parse-server/issues/10499)) ([3fad4fb](https://github.com/parse-community/parse-server/commit/3fad4fb1c4b41f51dab96532245bb302d2be30e6))
|
||||
* Middleware route checks do not match routing-equivalent path variants (trailing slash, case) ([#10501](https://github.com/parse-community/parse-server/issues/10501)) ([f861210](https://github.com/parse-community/parse-server/commit/f8612109e3175399b4f814efcc961128de6143a5))
|
||||
* NumberOrBoolean config option (cluster) value not coerced from env/CLI ([#10531](https://github.com/parse-community/parse-server/issues/10531)) ([459786f](https://github.com/parse-community/parse-server/commit/459786fd41cc835ec650d951258f011737cdb4c9))
|
||||
* Pre-authentication denial of service via client version header regex backtracking ([GHSA-38m6-82c8-4xfm](https://github.com/parse-community/parse-server/security/advisories/GHSA-38m6-82c8-4xfm)) ([#10463](https://github.com/parse-community/parse-server/issues/10463)) ([56c159e](https://github.com/parse-community/parse-server/commit/56c159ec962d729df09ccaa5cc2537751511e375))
|
||||
* rateLimit on exact static routes is bypassed by appending a query string ([#10500](https://github.com/parse-community/parse-server/issues/10500)) ([880e8e6](https://github.com/parse-community/parse-server/commit/880e8e6929fd62ed3680b138613bcfdcd572db07))
|
||||
* Relation `$relatedTo` query bypasses `protectedFields` and owning-object ACL ([GHSA-wmwx-jr2p-4j4r](https://github.com/parse-community/parse-server/security/advisories/GHSA-wmwx-jr2p-4j4r)) ([#10493](https://github.com/parse-community/parse-server/issues/10493)) ([43658f1](https://github.com/parse-community/parse-server/commit/43658f1fd83689b24a4350094f1071ac555ac9b3))
|
||||
* Server option routeAllowList is bypassable through batch sub-requests ([GHSA-p84r-h6rx-f2xr](https://github.com/parse-community/parse-server/security/advisories/GHSA-p84r-h6rx-f2xr)) ([#10482](https://github.com/parse-community/parse-server/issues/10482)) ([552c6dd](https://github.com/parse-community/parse-server/commit/552c6dd754638c9f546fbceecd2ba0f7225a95d1))
|
||||
* Stored XSS via malformed Content-Type bypassing file upload extension blocklist ([GHSA-r899-h629-j84r](https://github.com/parse-community/parse-server/security/advisories/GHSA-r899-h629-j84r)) ([#10521](https://github.com/parse-community/parse-server/issues/10521)) ([cce91e5](https://github.com/parse-community/parse-server/commit/cce91e554818492d1b153c46dc3b91fa6e0309bc))
|
||||
* Stored XSS via non-standard file extension bypassing file upload extension blocklist ([GHSA-v8x7-r927-cc93](https://github.com/parse-community/parse-server/security/advisories/GHSA-v8x7-r927-cc93)) ([#10505](https://github.com/parse-community/parse-server/issues/10505)) ([be12a60](https://github.com/parse-community/parse-server/commit/be12a60d65b6e140481882037fb896b1f951df50))
|
||||
* Stored XSS via trailing-dot filename bypassing file upload extension blocklist ([GHSA-7wqv-xjf3-x35v](https://github.com/parse-community/parse-server/security/advisories/GHSA-7wqv-xjf3-x35v)) ([#10489](https://github.com/parse-community/parse-server/issues/10489)) ([66484ce](https://github.com/parse-community/parse-server/commit/66484ce8fdd87a5d4c23bf9e40f7ea379b4dce79))
|
||||
|
||||
### Features
|
||||
|
||||
* Add option to disallow aggregation pipelines for the read-only master key ([#10517](https://github.com/parse-community/parse-server/issues/10517)) ([816078f](https://github.com/parse-community/parse-server/commit/816078fff7f95f333a99c1e2d7166a585742d466))
|
||||
|
||||
# [9.9.0](https://github.com/parse-community/parse-server/compare/9.8.0...9.9.0) (2026-05-01)
|
||||
|
||||
|
||||
|
||||
Generated
+2598
-1983
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "9.10.0-alpha.8",
|
||||
"version": "9.10.1-alpha.6",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -33,7 +33,7 @@
|
||||
"cors": "2.8.6",
|
||||
"express": "5.2.1",
|
||||
"express-rate-limit": "8.3.1",
|
||||
"follow-redirects": "1.15.11",
|
||||
"follow-redirects": "1.16.0",
|
||||
"graphql": "16.13.2",
|
||||
"graphql-list-fields": "2.0.4",
|
||||
"graphql-relay": "0.10.2",
|
||||
@@ -60,17 +60,17 @@
|
||||
"tv4": "1.3.0",
|
||||
"winston": "3.19.0",
|
||||
"winston-daily-rotate-file": "5.0.0",
|
||||
"ws": "8.20.0"
|
||||
"ws": "8.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "3.0.0",
|
||||
"@actions/core": "3.0.1",
|
||||
"@apollo/client": "3.13.8",
|
||||
"@babel/cli": "7.28.6",
|
||||
"@babel/core": "7.29.0",
|
||||
"@babel/core": "7.29.7",
|
||||
"@babel/eslint-parser": "7.28.6",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.27.1",
|
||||
"@babel/preset-env": "7.29.2",
|
||||
"@babel/preset-env": "7.29.7",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@saithodev/semantic-release-backmerge": "4.0.1",
|
||||
"@semantic-release/changelog": "6.0.3",
|
||||
@@ -78,16 +78,16 @@
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@semantic-release/github": "12.0.6",
|
||||
"@semantic-release/npm": "13.0.0",
|
||||
"@semantic-release/release-notes-generator": "14.1.0",
|
||||
"@semantic-release/release-notes-generator": "14.1.1",
|
||||
"all-node-versions": "13.0.1",
|
||||
"apollo-upload-client": "18.0.1",
|
||||
"clean-jsdoc-theme": "4.3.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-env": "10.1.0",
|
||||
"deep-diff": "1.0.2",
|
||||
"eslint": "9.27.0",
|
||||
"eslint-plugin-expect-type": "0.6.2",
|
||||
"eslint-plugin-unused-imports": "4.4.1",
|
||||
"form-data": "4.0.5",
|
||||
"form-data": "4.0.6",
|
||||
"globals": "17.3.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"jasmine": "6.1.0",
|
||||
@@ -99,14 +99,14 @@
|
||||
"madge": "8.0.0",
|
||||
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
|
||||
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
|
||||
"mongodb-runner": "5.9.3",
|
||||
"mongodb-runner": "6.8.3",
|
||||
"node-abort-controller": "3.1.1",
|
||||
"node-fetch": "3.3.2",
|
||||
"nyc": "17.1.0",
|
||||
"prettier": "3.8.1",
|
||||
"semantic-release": "25.0.3",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.58.0",
|
||||
"typescript-eslint": "8.59.1",
|
||||
"yaml": "2.8.3"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
* To rebuild the definitions file, run
|
||||
* `$ node resources/buildConfigDefinitions.js`
|
||||
*/
|
||||
const parsers = require('../src/Options/parsers');
|
||||
|
||||
/** The types of nested options. */
|
||||
const nestedOptionTypes = [
|
||||
@@ -190,6 +189,8 @@ function mapperFor(elt, t) {
|
||||
}
|
||||
|
||||
function parseDefaultValue(elt, value, t) {
|
||||
/* istanbul ignore next: lazy require (not module scope) so specs don't double-instrument parsers.js; only reached by `npm run definitions` */
|
||||
const parsers = require('../src/Options/parsers');
|
||||
let literalValue;
|
||||
if (t.isStringTypeAnnotation(elt)) {
|
||||
if (value == '""' || value == "''") {
|
||||
|
||||
@@ -504,6 +504,27 @@ describe('cloud validator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not leave an unhandled rejection when multiple fields fail validation (#8826)', async () => {
|
||||
const rejections = [];
|
||||
const onUnhandledRejection = reason => rejections.push(reason);
|
||||
process.on('unhandledRejection', onUnhandledRejection);
|
||||
try {
|
||||
Parse.Cloud.define('hello', () => 'Hello world!', {
|
||||
fields: {
|
||||
type: { type: String, options: ['Option A', 'Option B'] },
|
||||
project: { required: true },
|
||||
},
|
||||
});
|
||||
await expectAsync(Parse.Cloud.run('hello', { type: 'Invalid' })).toBeRejectedWith(
|
||||
jasmine.objectContaining({ code: Parse.Error.VALIDATION_ERROR })
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(rejections).toEqual([]);
|
||||
} finally {
|
||||
process.removeListener('unhandledRejection', onUnhandledRejection);
|
||||
}
|
||||
});
|
||||
|
||||
it('set params options function', done => {
|
||||
Parse.Cloud.define(
|
||||
'hello',
|
||||
|
||||
@@ -1298,6 +1298,113 @@ describe('Installations', () => {
|
||||
// TODO: Do we need to support _tombstone disabling of installations?
|
||||
// TODO: Test deletion, badge increments
|
||||
|
||||
describe('access control for non-master clients', () => {
|
||||
const anonymousHeaders = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
|
||||
it('blocks the find operation for an unauthenticated client', async () => {
|
||||
await rest.create(config, auth.nobody(config), '_Installation', {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
});
|
||||
let error;
|
||||
try {
|
||||
await request({
|
||||
method: 'GET',
|
||||
headers: anonymousHeaders,
|
||||
url: 'http://localhost:8378/1/installations',
|
||||
});
|
||||
fail('find should have been rejected');
|
||||
return;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(error.data.error).toBe('Permission denied');
|
||||
});
|
||||
|
||||
it('blocks the delete operation for an unauthenticated client', async () => {
|
||||
const created = await rest.create(config, auth.nobody(config), '_Installation', {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
});
|
||||
let error;
|
||||
try {
|
||||
await request({
|
||||
method: 'DELETE',
|
||||
headers: anonymousHeaders,
|
||||
url: 'http://localhost:8378/1/installations/' + created.response.objectId,
|
||||
});
|
||||
fail('delete should have been rejected');
|
||||
return;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(error.data.error).toBe('Permission denied');
|
||||
// The row is still present: the anonymous delete did not take effect.
|
||||
const remaining = await database.adapter.find('_Installation', installationSchema, {}, {});
|
||||
expect(remaining.length).toBe(1);
|
||||
});
|
||||
|
||||
it('blocks the find operation for an authenticated non-master user', async () => {
|
||||
// Even a logged-in user cannot enumerate installations, so another
|
||||
// device's objectId cannot be discovered through an authenticated session.
|
||||
const user = await Parse.User.signUp('installation-acl-user', 'pass-12345678');
|
||||
await rest.create(config, auth.nobody(config), '_Installation', {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
});
|
||||
let error;
|
||||
try {
|
||||
await request({
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...anonymousHeaders,
|
||||
'X-Parse-Session-Token': user.getSessionToken(),
|
||||
},
|
||||
url: 'http://localhost:8378/1/installations',
|
||||
});
|
||||
fail('find should have been rejected');
|
||||
return;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(error.data.error).toBe('Permission denied');
|
||||
});
|
||||
|
||||
it('blocks the delete operation for an authenticated non-master user', async () => {
|
||||
const user = await Parse.User.signUp('installation-acl-user', 'pass-12345678');
|
||||
const created = await rest.create(config, auth.nobody(config), '_Installation', {
|
||||
installationId: '12345678-abcd-abcd-abcd-123456789abc',
|
||||
deviceType: 'android',
|
||||
});
|
||||
let error;
|
||||
try {
|
||||
await request({
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
...anonymousHeaders,
|
||||
'X-Parse-Session-Token': user.getSessionToken(),
|
||||
},
|
||||
url: 'http://localhost:8378/1/installations/' + created.response.objectId,
|
||||
});
|
||||
fail('delete should have been rejected');
|
||||
return;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN);
|
||||
expect(error.data.error).toBe('Permission denied');
|
||||
// The row is still present: the authenticated non-master delete did not take effect.
|
||||
const remaining = await database.adapter.find('_Installation', installationSchema, {}, {});
|
||||
expect(remaining.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deviceToken deduplication on new install (no installationId match)', () => {
|
||||
const { randomUUID } = require('crypto');
|
||||
const installationSchema = {
|
||||
|
||||
@@ -5375,6 +5375,22 @@ describe('Parse.Query testing', () => {
|
||||
expect(result.executionStats).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it_only_db('mongo')('does not run afterFind on explain results', async () => {
|
||||
let afterFindCalled = false;
|
||||
Parse.Cloud.afterFind('AfterFindExplain', () => {
|
||||
afterFindCalled = true;
|
||||
return []; // empty return would drop the explain plan if the trigger ran
|
||||
});
|
||||
const obj = new Parse.Object('AfterFindExplain');
|
||||
await obj.save();
|
||||
const query = new Parse.Query('AfterFindExplain');
|
||||
query.equalTo('objectId', obj.id);
|
||||
query.explain();
|
||||
const result = await query.find({ useMasterKey: true });
|
||||
expect(result.executionStats).not.toBeUndefined(); // plan passed through untouched
|
||||
expect(afterFindCalled).toBe(false); // afterFind skipped for explain
|
||||
});
|
||||
|
||||
it('should query with distinct within eachBatch and direct access enabled', async () => {
|
||||
await reconfigureServer({
|
||||
directAccess: true,
|
||||
|
||||
@@ -257,6 +257,71 @@ describe('Parse.Session', () => {
|
||||
expect(newSession.createdWith.authProvider).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not delete another user\'s session when creating a session via POST /classes/_Session', async () => {
|
||||
const victim = await Parse.User.signUp('dedupvictim', 'password');
|
||||
const attacker = await Parse.User.signUp('dedupattacker', 'password');
|
||||
const victimId = victim.id;
|
||||
const installationId = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
// Victim logs in on a known installation, creating a session with that installationId.
|
||||
const victimLogin = await request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/login',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': installationId,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { username: 'dedupvictim', password: 'password' },
|
||||
});
|
||||
const victimSessionToken = victimLogin.data.sessionToken;
|
||||
|
||||
// Another user creates a session while naming the victim as `user` and supplying
|
||||
// the victim's installationId. The session dedup must not delete the victim's session.
|
||||
await request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/_Session',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': attacker.getSessionToken(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
user: { __type: 'Pointer', className: '_User', objectId: victimId },
|
||||
installationId,
|
||||
sessionToken: 'r:someothertoken',
|
||||
},
|
||||
});
|
||||
|
||||
// The victim's session on that installation must still exist...
|
||||
const sessions = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/classes/_Session',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
});
|
||||
const victimSession = sessions.data.results.find(
|
||||
s => s.installationId === installationId && s.user && s.user.objectId === victimId
|
||||
);
|
||||
expect(victimSession).toBeDefined();
|
||||
|
||||
// ...and the victim's session token must still authenticate.
|
||||
const meResponse = await request({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:8378/1/users/me',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Session-Token': victimSessionToken,
|
||||
},
|
||||
});
|
||||
expect(meResponse.data.objectId).toBe(victimId);
|
||||
});
|
||||
|
||||
it('should reject expiresAt when updating a session via PUT', async () => {
|
||||
const user = await Parse.User.signUp('sessionupdateuser1', 'password');
|
||||
const sessionToken = user.getSessionToken();
|
||||
|
||||
@@ -346,13 +346,13 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
liveQuery: {
|
||||
env: 'PARSE_SERVER_LIVE_QUERY',
|
||||
help: "parse-server's LiveQuery configuration object",
|
||||
help: "Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server.",
|
||||
action: parsers.objectParser,
|
||||
type: 'LiveQueryOptions',
|
||||
},
|
||||
liveQueryServerOptions: {
|
||||
env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS',
|
||||
help: 'Live query server configuration options (will start the liveQuery server)',
|
||||
help: 'Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`.',
|
||||
action: parsers.objectParser,
|
||||
type: 'LiveQueryServerOptions',
|
||||
},
|
||||
@@ -639,7 +639,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
startLiveQueryServer: {
|
||||
env: 'PARSE_SERVER_START_LIVE_QUERY_SERVER',
|
||||
help: 'Starts the liveQuery server',
|
||||
help: 'Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`.',
|
||||
action: parsers.booleanParser,
|
||||
},
|
||||
trustProxy: {
|
||||
|
||||
+3
-3
@@ -65,8 +65,8 @@
|
||||
* @property {InstallationOptions} installation Options controlling how Parse Server deduplicates `_Installation` records that share the same `deviceToken`.
|
||||
* @property {String} javascriptKey Key for the Javascript SDK
|
||||
* @property {Boolean} jsonLogs Log as structured JSON objects
|
||||
* @property {LiveQueryOptions} liveQuery parse-server's LiveQuery configuration object
|
||||
* @property {LiveQueryServerOptions} liveQueryServerOptions Live query server configuration options (will start the liveQuery server)
|
||||
* @property {LiveQueryOptions} liveQuery Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server.
|
||||
* @property {LiveQueryServerOptions} liveQueryServerOptions Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`.
|
||||
* @property {Adapter<LoggerAdapter>} loggerAdapter Adapter module for the logging sub-system
|
||||
* @property {String} logLevel Sets the level for logs
|
||||
* @property {LogLevels} logLevels (Optional) Overrides the log levels used internally by Parse Server to log events.
|
||||
@@ -115,7 +115,7 @@
|
||||
* @property {String} serverURL The URL to Parse Server.<br><br>⚠️ Certain server features or adapters may require Parse Server to be able to call itself by making requests to the URL set in `serverURL`. If a feature requires this, it is mentioned in the documentation. In that case ensure that the URL is accessible from the server itself.
|
||||
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
|
||||
* @property {Boolean} silent Disables console output
|
||||
* @property {Boolean} startLiveQueryServer Starts the liveQuery server
|
||||
* @property {Boolean} startLiveQueryServer Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`.
|
||||
* @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
|
||||
* @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
|
||||
|
||||
@@ -288,7 +288,7 @@ export interface ParseServerOptions {
|
||||
/* custom pages for password validation and reset
|
||||
:DEFAULT: {} */
|
||||
customPages: ?CustomPagesOptions;
|
||||
/* parse-server's LiveQuery configuration object */
|
||||
/* Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server. */
|
||||
liveQuery: ?LiveQueryOptions;
|
||||
/* Session duration, in seconds, defaults to 1 year
|
||||
:DEFAULT: 31536000 */
|
||||
@@ -347,9 +347,9 @@ export interface ParseServerOptions {
|
||||
/* The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
|
||||
:DEFAULT: false */
|
||||
trustProxy: ?any;
|
||||
/* Starts the liveQuery server */
|
||||
/* Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`. */
|
||||
startLiveQueryServer: ?boolean;
|
||||
/* Live query server configuration options (will start the liveQuery server) */
|
||||
/* Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`. */
|
||||
liveQueryServerOptions: ?LiveQueryServerOptions;
|
||||
/* Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.
|
||||
:ENV: PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS
|
||||
|
||||
+2
-2
@@ -1121,8 +1121,8 @@ _UnsafeRestQuery.prototype.runAfterFindTrigger = function () {
|
||||
if (!hasAfterFindHook) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Skip Aggregate and Distinct Queries
|
||||
if (this.findOptions.pipeline || this.findOptions.distinct) {
|
||||
// Skip Aggregate, Distinct and Explain Queries
|
||||
if (this.findOptions.pipeline || this.findOptions.distinct || this.findOptions.explain) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -1151,6 +1151,14 @@ RestWrite.prototype.deleteEmailResetTokenIfNeeded = function () {
|
||||
};
|
||||
|
||||
RestWrite.prototype.destroyDuplicatedSessions = function () {
|
||||
// Skip if the response is already set, matching the other write-pipeline steps
|
||||
// (runDatabaseOperation, runAfterSaveTrigger). A non-master POST /classes/_Session
|
||||
// create has handleSession() set this.response before this runs, so this guard
|
||||
// prevents the dedup delete from acting on the client-supplied `user`/`installationId`
|
||||
// rather than on the server-generated session data.
|
||||
if (this.response) {
|
||||
return;
|
||||
}
|
||||
// Only for _Session, and at creation time
|
||||
if (this.className != '_Session' || this.query) {
|
||||
return;
|
||||
|
||||
+3
-3
@@ -823,7 +823,7 @@ async function builtInTriggerValidator(options, request, auth) {
|
||||
requiredParam(key);
|
||||
}
|
||||
} else {
|
||||
const optionPromises = [];
|
||||
const optionValidations = [];
|
||||
for (const key in options.fields) {
|
||||
const opt = options.fields[key];
|
||||
let val = params[key];
|
||||
@@ -858,12 +858,12 @@ async function builtInTriggerValidator(options, request, auth) {
|
||||
}
|
||||
}
|
||||
if (opt.options) {
|
||||
optionPromises.push(validateOptions(opt, key, val));
|
||||
optionValidations.push([opt, key, val]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(optionPromises);
|
||||
await Promise.all(optionValidations.map(([o, k, v]) => validateOptions(o, k, v)));
|
||||
}
|
||||
let userRoles = options.requireAnyUserRoles;
|
||||
let requireAllRoles = options.requireAllUserRoles;
|
||||
|
||||
Reference in New Issue
Block a user