mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e42be5c526 | ||
|
|
309f64ced8 | ||
|
|
eb2952fff7 | ||
|
|
5be375dec2 | ||
|
|
ed0baa87af | ||
|
|
ba2b0a9cb9 | ||
|
|
a8aef820af | ||
|
|
af4a0417a9 | ||
|
|
852bb4782a | ||
|
|
9c414804ac | ||
|
|
ff5b39123b | ||
|
|
2aeae76d80 | ||
|
|
065141f00e | ||
|
|
0575952509 | ||
|
|
bc07a3ff3a | ||
|
|
c5cf282d11 | ||
|
|
e2333ba262 | ||
|
|
892c6f94d5 | ||
|
|
0d6f9e951d | ||
|
|
765cfd02dc | ||
|
|
ccd3d86137 | ||
|
|
693d72060d | ||
|
|
2b7f772a1b | ||
|
|
443a509905 | ||
|
|
2830021990 | ||
|
|
d2d12d3d69 | ||
|
|
1099ebbcd8 | ||
|
|
4c50c4d11c | ||
|
|
e9d23830a3 | ||
|
|
01096b360a | ||
|
|
effed92cab | ||
|
|
9cdc6ca5b3 | ||
|
|
158a974c89 | ||
|
|
1593575a87 | ||
|
|
ef9ee66640 | ||
|
|
d5db318db8 | ||
|
|
9fa80eb4ed | ||
|
|
ff16839450 | ||
|
|
0c1b75fcbe | ||
|
|
69781ce0cc | ||
|
|
2eebc68f21 | ||
|
|
79e00e1dee | ||
|
|
46c9a91627 | ||
|
|
33dcf6dd7b | ||
|
|
1f6e19fcce | ||
|
|
b2a2a7e1f5 | ||
|
|
50072bdd38 | ||
|
|
2dceec7359 | ||
|
|
f5ef2e9162 | ||
|
|
23a3488f15 | ||
|
|
3e68491df0 | ||
|
|
5ace99d542 | ||
|
|
9b344da9d0 | ||
|
|
e569f402b1 | ||
|
|
972b800ae4 | ||
|
|
d35cd47c08 | ||
|
|
971adb5438 | ||
|
|
a48015c3b0 | ||
|
|
7029b274ca | ||
|
|
be37266595 | ||
|
|
4bd34b189b | ||
|
|
f90461e693 | ||
|
|
3b92fa1ca9 | ||
|
|
66347dc44a | ||
|
|
8ee0445c0a | ||
|
|
2923833b25 | ||
|
|
6a54dac24d | ||
|
|
32b7194139 | ||
|
|
200d4ba9a5 | ||
|
|
6962bfac65 | ||
|
|
e91e388366 | ||
|
|
4f114068a1 | ||
|
|
e94a08ffdf |
@@ -30,7 +30,7 @@ jobs:
|
||||
- run: npx semantic-release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Determine tag on current commit
|
||||
id: tag
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
},
|
||||
"es6": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -42,8 +42,7 @@ Details:
|
||||
- Suitable environment: experimental
|
||||
|
||||
|
||||
[log_release]: https://github.com/parse-community/parse-server/blob/release/CHANGELOG.md
|
||||
<!--[log_release]: https://github.com/parse-community/parse-server/blob/release/changelogs/CHANGELOG_release.md -->
|
||||
[log_release]: https://github.com/parse-community/parse-server/blob/release/changelogs/CHANGELOG_release.md
|
||||
[log_beta]: https://github.com/parse-community/parse-server/blob/beta/changelogs/CHANGELOG_beta.md
|
||||
[log_alpha]: https://github.com/parse-community/parse-server/blob/alpha/changelogs/CHANGELOG_alpha.md
|
||||
[branch_release]: https://github.com/parse-community/parse-server/tree/release
|
||||
|
||||
+62
-5
@@ -22,6 +22,11 @@
|
||||
- [Pull Request](#pull-request)
|
||||
- [Breaking Change](#breaking-change)
|
||||
- [Merging](#merging)
|
||||
- [Breaking Change](#breaking-change-1)
|
||||
- [Reverting](#reverting)
|
||||
- [Releasing](#releasing)
|
||||
- [General Considerations](#general-considerations)
|
||||
- [Major Release / Long-Term-Support](#major-release--long-term-support)
|
||||
- [Versioning](#versioning)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
|
||||
@@ -303,7 +308,7 @@ For release automation, the title of pull requests needs to be written in a defi
|
||||
```
|
||||
|
||||
The _type_ is the category of change that is made, possible types are:
|
||||
- `feat` - add a new feature
|
||||
- `feat` - add a new feature or improve an existing feature
|
||||
- `fix` - fix a bug
|
||||
- `refactor` - refactor code without impact on features or performance
|
||||
- `docs` - add or edit code comments, documentation, GitHub pages
|
||||
@@ -335,8 +340,13 @@ If a pull request contains a braking change, the description of the pull request
|
||||
|
||||
The following guide is for anyone who merges a contributor pull request into the working branch, the working branch into a release branch, a release branch into another release branch, or any other direct commits such as hotfixes into release branches or the working branch.
|
||||
|
||||
- For changelog generation, only the commit message set when merging the pull request is relevant. The title and description of the GitHub pull request as authored by the contributor have no influence on the changelog generation. However, the title of the GitHub pull request should be used as the commit message.
|
||||
- If the pull request contains a breaking change, the commit message must contain the phrase `BREAKING CHANGE`, capitalized and without any formatting, followed by a short description of the breaking change and ideally how the developer should address it, all in a single line. This line should contain more details focusing on the "breaking” aspect of the change and is intended to assist the developer in adapting. Keep it concise, as it will become part of the changelog entry, for example:
|
||||
- A contributor pull request must be merged into the working branch using `Squash and Merge`, to create a single commit message that describes the change.
|
||||
- A release branch or the default branch must be merged into another release branch using `Merge Commit`, to preserve each individual commit message that describes its respective change.
|
||||
- For changelog generation, only the commit message set when merging the pull request is relevant. The title and description of the GitHub pull request as authored by the contributor have no influence on the changelog generation. However, the title of the GitHub pull request should be used as the commit message. See the following chapters for considerations in special scenarios, e.g. merging a breaking change or reverting a commit.
|
||||
|
||||
### Breaking Change
|
||||
|
||||
If the pull request contains a breaking change, the commit message must contain the phrase `BREAKING CHANGE`, capitalized and without any formatting, followed by a short description of the breaking change and ideally how the developer should address it, all in a single line. This line should contain more details focusing on the "breaking” aspect of the change and is intended to assist the developer in adapting. Keep it concise, as it will become part of the changelog entry, for example:
|
||||
|
||||
```
|
||||
fix: remove handle from door
|
||||
@@ -344,8 +354,55 @@ The following guide is for anyone who merges a contributor pull request into the
|
||||
BREAKING CHANGE: You cannot open the door anymore by using a handle. See the [#migration guide](http://example.com) for more details.
|
||||
```
|
||||
Keep in mind that in a repository with release automation, merging such a commit message will trigger a release with a major version increment.
|
||||
- A contributor pull request must be merged into the working branch using `Squash and Merge`, to create a single commit message that describes the change.
|
||||
- A release branch or the default branch must be merged into another release branch using `Merge Commit`, to preserve each individual commit message that describes its respective change.
|
||||
|
||||
### Reverting
|
||||
|
||||
If the commit reverts a previous commit, use the prefix `revert:`, followed by the header of the reverted commit. In the body of the commit message add `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted. For example:
|
||||
|
||||
```
|
||||
revert: fix: remove handle from door
|
||||
|
||||
This reverts commit 1234567890abcdef.
|
||||
```
|
||||
|
||||
⚠️ A `revert` prefix will *always* trigger a release. Generally, a commit that did not trigger a release when it was initially merged should also not trigger a release when it is reverted. For example, do not use the `revert` prefix when reverting a commit that has a `ci` prefix:
|
||||
|
||||
```
|
||||
ci: add something
|
||||
```
|
||||
is reverted with:
|
||||
```
|
||||
ci: remove something
|
||||
```
|
||||
instead of:
|
||||
```
|
||||
revert: ci: add something
|
||||
|
||||
This reverts commit 1234567890abcdef.
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
### General Considerations
|
||||
|
||||
- The `package-lock.json` file has to be deleted and recreated by npm from scratch in regular intervals using the `npm i` command. It is not enough to only update the file via automated security pull requests (e.g. dependabot, snyk), that can create inconsistencies between sub-devependencies of a dependency and increase the chances of vulnerabilities. The file should be recreated once every release cycle which is usually monthly.
|
||||
|
||||
### Major Release / Long-Term-Support
|
||||
|
||||
Long-Term-Support (LTS) is provided for the previous Parse Server major version. For example, Parse Server 4.x will receive security updates until Parse Server 5.x is superseded by Parse Server 6.x and becomes the new LTS version. While the current major version is published on branch `release`, a LTS version is published on branch `release-#.x.x`, for example `release-4.x.x` for the Parse Server 4.x LTS branch.
|
||||
|
||||
#### Preparing Release
|
||||
|
||||
The following changes are done in the `alpha` branch, before publishing the last `beta` version that will eventually become the major release. This way the changes trickle naturally through all branches and code consistency is ensured among branches.
|
||||
|
||||
- Make sure all [deprecations](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md) are reflected in code, old code is removed and the deprecations table is updated.
|
||||
- Add the future LTS branch `release-#.x.x` to the branch list in [release.config.js](https://github.com/parse-community/parse-server/blob/alpha/release.config.js) so that the branch will later be recognized for release automation.
|
||||
|
||||
#### Publishing Release
|
||||
|
||||
1. Create LTS branch `release-#.x.x` off the latest version tag on `release` branch.
|
||||
2. Create temporary branch `build-release` off branch `beta` and create a pull request with `release` as the base branch.
|
||||
3. Merge branch `build-release` into `release`. Given that there will be breaking changes, a new major release will be created. In the unlikely case that there have been no breaking changes between the previous major release and the upcoming release, a major version increment has to be triggered manually. See the docs of the release automation framework for how to do that.
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
# [5.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.2...5.2.0-alpha.3) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
|
||||
|
||||
# [5.2.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.1...5.2.0-alpha.2) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
|
||||
|
||||
# [5.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-alpha.1) (2022-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.28...5.0.0-alpha.29) (2022-03-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* bump required node engine to >=12.22.10 ([#7846](https://github.com/parse-community/parse-server/issues/7846)) ([5ace99d](https://github.com/parse-community/parse-server/commit/5ace99d542a11e422af46d9fd6b1d3d2513b34cf))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires Node.js version >=12.22.10. ([5ace99d](5ace99d))
|
||||
|
||||
# [5.0.0-alpha.28](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.27...5.0.0-alpha.28) (2022-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7844](https://github.com/parse-community/parse-server/issues/7844)) ([e569f40](https://github.com/parse-community/parse-server/commit/e569f402b1fd8648fb0d1523b71b2a03273902a5))
|
||||
|
||||
# [5.0.0-alpha.27](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.26...5.0.0-alpha.27) (2022-03-12)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# [5.2.0-beta.2](https://github.com/parse-community/parse-server/compare/5.2.0-beta.1...5.2.0-beta.2) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
|
||||
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
|
||||
|
||||
# [5.2.0-beta.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-beta.1) (2022-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
|
||||
|
||||
# [5.0.0-beta.10](https://github.com/parse-community/parse-server/compare/5.0.0-beta.9...5.0.0-beta.10) (2022-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adding or modifying a nested property requires addField permissions ([#7679](https://github.com/parse-community/parse-server/issues/7679)) ([6a6248b](https://github.com/parse-community/parse-server/commit/6a6248b6cb2e732d17131e18e659943b894ed2f1))
|
||||
* bump nanoid from 3.1.25 to 3.2.0 ([#7781](https://github.com/parse-community/parse-server/issues/7781)) ([f5f63bf](https://github.com/parse-community/parse-server/commit/f5f63bfc64d3481ed944ceb5e9f50b33dccd1ce9))
|
||||
* bump node-fetch from 2.6.1 to 3.1.1 ([#7782](https://github.com/parse-community/parse-server/issues/7782)) ([9082351](https://github.com/parse-community/parse-server/commit/90823514113a1a085ebc818f7109b3fd7591346f))
|
||||
* node engine compatibility did not include node 16 ([#7739](https://github.com/parse-community/parse-server/issues/7739)) ([ea7c014](https://github.com/parse-community/parse-server/commit/ea7c01400f992a1263543706fe49b6174758a2d6))
|
||||
* node engine range has no upper limit to exclude incompatible node versions ([#7692](https://github.com/parse-community/parse-server/issues/7692)) ([573558d](https://github.com/parse-community/parse-server/commit/573558d3adcbcc6222c92003829867e1a73eef94))
|
||||
* package.json & package-lock.json to reduce vulnerabilities ([#7823](https://github.com/parse-community/parse-server/issues/7823)) ([5ca2288](https://github.com/parse-community/parse-server/commit/5ca228882332b65f3ac05407e6e4da1ee3ef3749))
|
||||
* schema cache not cleared in some cases ([#7678](https://github.com/parse-community/parse-server/issues/7678)) ([5af6e5d](https://github.com/parse-community/parse-server/commit/5af6e5dfaa129b1a350afcba4fb381b21c4cc35d))
|
||||
* security upgrade follow-redirects from 1.14.6 to 1.14.7 ([#7769](https://github.com/parse-community/parse-server/issues/7769)) ([8f5a861](https://github.com/parse-community/parse-server/commit/8f5a8618cfa7ed9a2a239a095abffa8f3fd8d31a))
|
||||
* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7801](https://github.com/parse-community/parse-server/issues/7801)) ([70088a9](https://github.com/parse-community/parse-server/commit/70088a95a78393da2a4ac68be81e63107747626a))
|
||||
* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7844](https://github.com/parse-community/parse-server/issues/7844)) ([e569f40](https://github.com/parse-community/parse-server/commit/e569f402b1fd8648fb0d1523b71b2a03273902a5))
|
||||
* server crash using GraphQL due to missing @apollo/client peer dependency ([#7787](https://github.com/parse-community/parse-server/issues/7787)) ([08089d6](https://github.com/parse-community/parse-server/commit/08089d6fcbb215412448ce7d92b21b9fe6c929f2))
|
||||
* unable to use objectId size higher than 19 on GraphQL API ([#7627](https://github.com/parse-community/parse-server/issues/7627)) ([ed86c80](https://github.com/parse-community/parse-server/commit/ed86c807721cc52a1a5a9dea0b768717eec269ed))
|
||||
* upgrade mime from 2.5.2 to 3.0.0 ([#7725](https://github.com/parse-community/parse-server/issues/7725)) ([f5ef98b](https://github.com/parse-community/parse-server/commit/f5ef98bde32083403c0e30a12162fcc1e52cac37))
|
||||
* upgrade parse from 3.3.1 to 3.4.0 ([#7723](https://github.com/parse-community/parse-server/issues/7723)) ([d4c1f47](https://github.com/parse-community/parse-server/commit/d4c1f473073764cb0570c633fc4a30669c2ce889))
|
||||
* upgrade winston from 3.5.0 to 3.5.1 ([#7820](https://github.com/parse-community/parse-server/issues/7820)) ([4af253d](https://github.com/parse-community/parse-server/commit/4af253d1f8654a6f57b5137ad310cdacadc922cc))
|
||||
|
||||
### Features
|
||||
|
||||
* add Cloud Code context to `ParseObject.fetch` ([#7779](https://github.com/parse-community/parse-server/issues/7779)) ([315290d](https://github.com/parse-community/parse-server/commit/315290d16110110938f80a6b779cc2d1db58c552))
|
||||
* add Idempotency to Postgres ([#7750](https://github.com/parse-community/parse-server/issues/7750)) ([0c3feaa](https://github.com/parse-community/parse-server/commit/0c3feaaa1751964c0db89f25674935c3354b1538))
|
||||
* add support for Node 16 ([#7707](https://github.com/parse-community/parse-server/issues/7707)) ([45cc58c](https://github.com/parse-community/parse-server/commit/45cc58c7e5e640a46c5d508019a3aa81242964b1))
|
||||
* bump required node engine to >=12.22.10 ([#7846](https://github.com/parse-community/parse-server/issues/7846)) ([5ace99d](https://github.com/parse-community/parse-server/commit/5ace99d542a11e422af46d9fd6b1d3d2513b34cf))
|
||||
* support `postgresql` protocol in database URI ([#7757](https://github.com/parse-community/parse-server/issues/7757)) ([caf4a23](https://github.com/parse-community/parse-server/commit/caf4a2341f554b28e3918c53e7e897a3ca47bf8b))
|
||||
* support relativeTime query constraint on Postgres ([#7747](https://github.com/parse-community/parse-server/issues/7747)) ([16b1b2a](https://github.com/parse-community/parse-server/commit/16b1b2a19714535ca805f2dbb3b561d8f6a519a7))
|
||||
* upgrade to MongoDB Node.js driver 4.x for MongoDB 5.0 support ([#7794](https://github.com/parse-community/parse-server/issues/7794)) ([f88aa2a](https://github.com/parse-community/parse-server/commit/f88aa2a62a533e5344d1c13dd38c5a0b283a480a))
|
||||
|
||||
### Reverts
|
||||
|
||||
* refactor: allow ES import for cloud string if package type is module ([b64640c](https://github.com/parse-community/parse-server/commit/b64640c5705f733798783e68d216e957044ef23c))
|
||||
* update node engine to 2.22.0 ([#7827](https://github.com/parse-community/parse-server/issues/7827)) ([f235412](https://github.com/parse-community/parse-server/commit/f235412c1b6c2b173b7531f285429ea7214b56a2))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires Node.js version >=12.22.10. ([5ace99d](5ace99d))
|
||||
* The MongoDB GridStore adapter has been removed. By default, Parse Server already uses GridFS, so if you do not manually use the GridStore adapter, you can ignore this change. ([f88aa2a](f88aa2a))
|
||||
* Removes official Node 15 support which has reached it end-of-life date. ([45cc58c](45cc58c))
|
||||
|
||||
# [5.0.0-beta.9](https://github.com/parse-community/parse-server/compare/5.0.0-beta.8...5.0.0-beta.9) (2022-03-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* bump required node engine to >=12.22.10 ([#7848](https://github.com/parse-community/parse-server/issues/7848)) ([23a3488](https://github.com/parse-community/parse-server/commit/23a3488f15511fafbe0e1d7ff0ef8355f9cb0215))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires Node.js version >=12.22.10. ([23a3488](23a3488))
|
||||
|
||||
# [5.0.0-beta.8](https://github.com/parse-community/parse-server/compare/5.0.0-beta.7...5.0.0-beta.8) (2022-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7843](https://github.com/parse-community/parse-server/issues/7843)) ([971adb5](https://github.com/parse-community/parse-server/commit/971adb54387b0ede31be05ca407d5f35b4575c83))
|
||||
|
||||
# [5.0.0-beta.7](https://github.com/parse-community/parse-server/compare/5.0.0-beta.6...5.0.0-beta.7) (2022-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7802](https://github.com/parse-community/parse-server/issues/7802)) ([7029b27](https://github.com/parse-community/parse-server/commit/7029b274ca87bc8058617f29865d683dc3b351a1))
|
||||
|
||||
# [5.0.0-beta.6](https://github.com/parse-community/parse-server/compare/5.0.0-beta.5...5.0.0-beta.6) (2022-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade follow-redirects from 1.14.2 to 1.14.7 ([#7772](https://github.com/parse-community/parse-server/issues/7772)) ([4bd34b1](https://github.com/parse-community/parse-server/commit/4bd34b189bc9f5aa2e70b7e7c1a456e91b6de773))
|
||||
|
||||
# [5.0.0-beta.5](https://github.com/parse-community/parse-server/compare/5.0.0-beta.4...5.0.0-beta.5) (2022-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* schema cache not cleared in some cases ([#7771](https://github.com/parse-community/parse-server/issues/7771)) ([3b92fa1](https://github.com/parse-community/parse-server/commit/3b92fa1ca9e8889127a32eba913d68309397ca2c))
|
||||
|
||||
# [5.0.0-beta.4](https://github.com/parse-community/parse-server/compare/5.0.0-beta.3...5.0.0-beta.4) (2021-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unable to use objectId size higher than 19 on GraphQL API ([#7722](https://github.com/parse-community/parse-server/issues/7722)) ([8ee0445](https://github.com/parse-community/parse-server/commit/8ee0445c0aeeb88dff2559b46ade408071d22143))
|
||||
|
||||
# [5.0.0-beta.3](https://github.com/parse-community/parse-server/compare/5.0.0-beta.2...5.0.0-beta.3) (2021-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* node engine range has no upper limit to exclude incompatible node versions ([#7693](https://github.com/parse-community/parse-server/issues/7693)) ([6a54dac](https://github.com/parse-community/parse-server/commit/6a54dac24d9fb63a44f311b8d414f4aa64140f32))
|
||||
|
||||
# [5.0.0-beta.2](https://github.com/parse-community/parse-server/compare/5.0.0-beta.1...5.0.0-beta.2) (2021-11-10)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* refactor: allow ES import for cloud string if package type is module ([#7691](https://github.com/parse-community/parse-server/issues/7691)) ([200d4ba](https://github.com/parse-community/parse-server/commit/200d4ba9a527016a65668738c7728696f443bd53))
|
||||
|
||||
# [5.0.0-beta.1](https://github.com/parse-community/parse-server/compare/4.5.0...5.0.0-beta.1) (2021-11-01)
|
||||
|
||||
### BREAKING CHANGES
|
||||
- Improved schema caching through database real-time hooks. Reduces DB queries, decreases Parse Query execution time and fixes a potential schema memory leak. If multiple Parse Server instances connect to the same DB (for example behind a load balancer), set the [Parse Server Option](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `databaseOptions.enableSchemaHooks: true` to enable this feature and keep the schema in sync across all instances. Failing to do so will cause a schema change to not propagate to other instances and re-syncing will only happen when these instances restart. The options `enableSingleSchemaCache` and `schemaCacheTTL` have been removed. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required. (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214)
|
||||
- Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (dblythy, Manuel Trezza) [#7071](https://github.com/parse-community/parse-server/pull/7071)
|
||||
- Removed [parse-server-simple-mailgun-adapter](https://github.com/parse-community/parse-server-simple-mailgun-adapter) dependency; to continue using the adapter it has to be explicitly installed (Manuel Trezza) [#7321](https://github.com/parse-community/parse-server/pull/7321)
|
||||
- Remove support for MongoDB 3.6 which has reached its End-of-Life date and PostgreSQL 10 (Manuel Trezza) [#7315](https://github.com/parse-community/parse-server/pull/7315)
|
||||
- Remove support for Node 10 which has reached its End-of-Life date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314)
|
||||
- Remove S3 Files Adapter from Parse Server, instead install separately as `@parse/s3-files-adapter` (Manuel Trezza) [#7324](https://github.com/parse-community/parse-server/pull/7324)
|
||||
- Remove Session field `restricted`; the field was a code artifact from a feature that never existed in Open Source Parse Server; if you have been using this field for custom purposes, consider that for new Parse Server installations the field does not exist anymore in the schema, and for existing installations the field default value `false` will not be set anymore when creating a new session (Manuel Trezza) [#7543](https://github.com/parse-community/parse-server/pull/7543)
|
||||
- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
|
||||
- To delete a field via the GraphQL API, the field value has to be set to `null`. Previously, setting a field value to `null` would save a null value in the database, which was not according to the [GraphQL specs](https://spec.graphql.org/June2018/#sec-Null-Value). To delete a file field use `file: null`, the previous way of using `file: { file: null }` has become obsolete. ([626fad2](626fad2))
|
||||
|
||||
### Notable Changes
|
||||
- Alphabetical ordered GraphQL API, improved GraphQL Schema cache system and fix GraphQL input reassign issue (Moumouls) [#7344](https://github.com/parse-community/parse-server/issues/7344)
|
||||
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)
|
||||
- EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#7128](https://github.com/parse-community/parse-server/pull/7128)
|
||||
- EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/pull/7231)
|
||||
- Added Deprecation Policy to govern the introduction of breaking changes in a phased pattern that is more predictable for developers (Manuel Trezza) [#7199](https://github.com/parse-community/parse-server/pull/7199)
|
||||
- Add REST API endpoint `/loginAs` to create session of any user with master key; allows to impersonate another user. (GormanFletcher) [#7406](https://github.com/parse-community/parse-server/pull/7406)
|
||||
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
|
||||
- Added Parse Server Configuration `enforcePrivateUsers`, which will remove public access by default on new Parse.Users (dblythy) [#7319](https://github.com/parse-community/parse-server/pull/7319)
|
||||
* add support for Postgres 14 ([#7644](https://github.com/parse-community/parse-server/issues/7644)) ([090350a](https://github.com/parse-community/parse-server/commit/090350a7a0fac945394ca1cb24b290316ef06aa7))
|
||||
* add user-defined schema and migrations ([#7418](https://github.com/parse-community/parse-server/issues/7418)) ([25d5c30](https://github.com/parse-community/parse-server/commit/25d5c30be2111be332eb779eb0697774a17da7af))
|
||||
* setting a field to null does not delete it via GraphQL API ([#7649](https://github.com/parse-community/parse-server/issues/7649)) ([626fad2](https://github.com/parse-community/parse-server/commit/626fad2e71017dcc62196c487de5f908fa43000b))
|
||||
* combined `and` query with relational query condition returns incorrect results ([#7593](https://github.com/parse-community/parse-server/issues/7593)) ([174886e](https://github.com/parse-community/parse-server/commit/174886e385e091c6bbd4a84891ef95f80b50d05c))
|
||||
|
||||
### Other Changes
|
||||
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)
|
||||
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
|
||||
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
|
||||
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)
|
||||
- Added convenience method `Parse.Cloud.sendEmail(...)` to send email via email adapter in Cloud Code (dblythy) [#7089](https://github.com/parse-community/parse-server/pull/7089)
|
||||
- LiveQuery support for $and, $nor, $containedBy, $geoWithin, $geoIntersects queries (dplewis) [#7113](https://github.com/parse-community/parse-server/pull/7113)
|
||||
- Supporting patterns in LiveQuery server's config parameter `classNames` (Nes-si) [#7131](https://github.com/parse-community/parse-server/pull/7131)
|
||||
- Added `requireAnyUserRoles` and `requireAllUserRoles` for Parse Cloud validator (dblythy) [#7097](https://github.com/parse-community/parse-server/pull/7097)
|
||||
- Support Facebook Limited Login (miguel-s) [#7219](https://github.com/parse-community/parse-server/pull/7219)
|
||||
- Removed Stage name check on aggregate pipelines (BRETT71) [#7237](https://github.com/parse-community/parse-server/pull/7237)
|
||||
- Retry transactions on MongoDB when it fails due to transient error (Antonio Davi Macedo Coelho de Castro) [#7187](https://github.com/parse-community/parse-server/pull/7187)
|
||||
- Bump tests to use Mongo 4.4.4 (Antonio Davi Macedo Coelho de Castro) [#7184](https://github.com/parse-community/parse-server/pull/7184)
|
||||
- Added new account lockout policy option `accountLockout.unlockOnPasswordReset` to automatically unlock account on password reset (Manuel Trezza) [#7146](https://github.com/parse-community/parse-server/pull/7146)
|
||||
- Test Parse Server continuously against all recent MongoDB versions that have not reached their end-of-life support date, added MongoDB compatibility table to Parse Server docs (Manuel Trezza) [#7161](https://github.com/parse-community/parse-server/pull/7161)
|
||||
- Test Parse Server continuously against all recent Node.js versions that have not reached their end-of-life support date, added Node.js compatibility table to Parse Server docs (Manuel Trezza) [7161](https://github.com/parse-community/parse-server/pull/7177)
|
||||
- Throw error on invalid Cloud Function validation configuration (dblythy) [#7154](https://github.com/parse-community/parse-server/pull/7154)
|
||||
- Allow Cloud Validator `options` to be async (dblythy) [#7155](https://github.com/parse-community/parse-server/pull/7155)
|
||||
- Optimize queries on classes with pointer permissions (Pedro Diaz) [#7061](https://github.com/parse-community/parse-server/pull/7061)
|
||||
- Test Parse Server continuously against all relevant Postgres versions (minor versions), added Postgres compatibility table to Parse Server docs (Corey Baker) [#7176](https://github.com/parse-community/parse-server/pull/7176)
|
||||
- Randomize test suite (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- Improve data consistency in Push and Job Status update (Diamond Lewis) [#7267](https://github.com/parse-community/parse-server/pull/7267)
|
||||
- Excluding keys that have trailing edges.node when performing GraphQL resolver (Chris Bland) [#7273](https://github.com/parse-community/parse-server/pull/7273)
|
||||
- Added centralized feature deprecation with standardized warning logs (Manuel Trezza) [#7303](https://github.com/parse-community/parse-server/pull/7303)
|
||||
- Use Node.js 15.13.0 in CI (Olle Jonsson) [#7312](https://github.com/parse-community/parse-server/pull/7312)
|
||||
- Fix file upload issue for S3 compatible storage (Linode, DigitalOcean) by avoiding empty tags property when creating a file (Ali Oguzhan Yildiz) [#7300](https://github.com/parse-community/parse-server/pull/7300)
|
||||
- Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332)
|
||||
- Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333)
|
||||
- Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341)
|
||||
- Fix select and excludeKey queries to properly accept JSON string arrays. Also allow nested fields in exclude (Corey Baker) [#7242](https://github.com/parse-community/parse-server/pull/7242)
|
||||
- Fix LiveQuery server crash when using $all query operator on a missing object key (Jason Posthuma) [#7421](https://github.com/parse-community/parse-server/pull/7421)
|
||||
- Added runtime deprecation warnings (Manuel Trezza) [#7451](https://github.com/parse-community/parse-server/pull/7451)
|
||||
- Add ability to pass context of an object via a header, X-Parse-Cloud-Context, for Cloud Code triggers. The header addition allows client SDK's to add context without injecting _context in the body of JSON objects (Corey Baker) [#7437](https://github.com/parse-community/parse-server/pull/7437)
|
||||
- Add CI check to add changelog entry (Manuel Trezza) [#7512](https://github.com/parse-community/parse-server/pull/7512)
|
||||
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
|
||||
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
|
||||
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
|
||||
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
|
||||
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
|
||||
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
|
||||
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)
|
||||
- refactor: deprecate `Parse.Cloud.httpRequest`; it is recommended to use a HTTP library instead. (Daniel Blyth) [#7595](https://github.com/parse-community/parse-server/pull/7595)
|
||||
- refactor: Modernize HTTPRequest tests (brandongregoryscott) [#7604](https://github.com/parse-community/parse-server/pull/7604)
|
||||
- Allow liveQuery on Session class (Daniel Blyth) [#7554](https://github.com/parse-community/parse-server/pull/7554)
|
||||
@@ -1,3 +1,198 @@
|
||||
## [5.2.4](https://github.com/parse-community/parse-server/compare/5.2.3...5.2.4) (2022-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* protected fields exposed via LiveQuery; this removes protected fields from the client response; this may be a breaking change if your app is currently expecting to receive these protected fields ([GHSA-crrq-vr9j-fxxh](https://github.com/parse-community/parse-server/security/advisories/GHSA-crrq-vr9j-fxxh)) (https://github.com/parse-community/parse-server/pull/8074) ([#8073](https://github.com/parse-community/parse-server/issues/8073)) ([309f64c](https://github.com/parse-community/parse-server/commit/309f64ced8700321df056fb3cc97f15007a00df1))
|
||||
|
||||
## [5.2.3](https://github.com/parse-community/parse-server/compare/5.2.2...5.2.3) (2022-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid file request not properly handled; this fixes a security vulnerability in which an invalid file request can crash the server ([GHSA-xw6g-jjvf-wwf9](https://github.com/parse-community/parse-server/security/advisories/GHSA-xw6g-jjvf-wwf9)) ([#8060](https://github.com/parse-community/parse-server/issues/8060)) ([5be375d](https://github.com/parse-community/parse-server/commit/5be375dec2fa35425c1003ae81c55995ac72af92))
|
||||
|
||||
## [5.2.2](https://github.com/parse-community/parse-server/compare/5.2.1...5.2.2) (2022-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* certificate in Apple Game Center auth adapter not validated; this fixes a security vulnerability in which authentication could be bypassed using a fake certificate; if you are using the Apple Gamer Center auth adapter it is your responsibility to keep its root certificate up-to-date and we advice you read the security advisory ([GHSA-rh9j-f5f8-rvgc](https://github.com/parse-community/parse-server/security/advisories/GHSA-rh9j-f5f8-rvgc)) ([ba2b0a9](https://github.com/parse-community/parse-server/commit/ba2b0a9cb9a568817a114b132a4c2e0911d76df1))
|
||||
|
||||
## [5.2.1](https://github.com/parse-community/parse-server/compare/5.2.0...5.2.1) (2022-05-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* authentication bypass and denial of service (DoS) vulnerabilities in Apple Game Center auth adapter (GHSA-qf8x-vqjv-92gr) ([#7962](https://github.com/parse-community/parse-server/issues/7962)) ([af4a041](https://github.com/parse-community/parse-server/commit/af4a0417a9f3c1e99b3793806b4b18e04d9fa999))
|
||||
|
||||
# [5.2.0](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
|
||||
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
|
||||
|
||||
### Features
|
||||
|
||||
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
|
||||
|
||||
## [5.1.1](https://github.com/parse-community/parse-server/compare/5.1.0...5.1.1) (2022-03-18)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* ci: temporarily disable breaking change detection ([#7861](https://github.com/parse-community/parse-server/issues/7861)) ([effed92](https://github.com/parse-community/parse-server/commit/effed92cabd88676fdf9eca2e079a4d8be017f1b))
|
||||
|
||||
# [5.1.0](https://github.com/parse-community/parse-server/compare/5.0.0...5.1.0) (2022-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adding or modifying a nested property requires addField permissions ([#7679](https://github.com/parse-community/parse-server/issues/7679)) ([6a6248b](https://github.com/parse-community/parse-server/commit/6a6248b6cb2e732d17131e18e659943b894ed2f1))
|
||||
* bump nanoid from 3.1.25 to 3.2.0 ([#7781](https://github.com/parse-community/parse-server/issues/7781)) ([f5f63bf](https://github.com/parse-community/parse-server/commit/f5f63bfc64d3481ed944ceb5e9f50b33dccd1ce9))
|
||||
* bump node-fetch from 2.6.1 to 3.1.1 ([#7782](https://github.com/parse-community/parse-server/issues/7782)) ([9082351](https://github.com/parse-community/parse-server/commit/90823514113a1a085ebc818f7109b3fd7591346f))
|
||||
* node engine compatibility did not include node 16 ([#7739](https://github.com/parse-community/parse-server/issues/7739)) ([ea7c014](https://github.com/parse-community/parse-server/commit/ea7c01400f992a1263543706fe49b6174758a2d6))
|
||||
* node engine range has no upper limit to exclude incompatible node versions ([#7692](https://github.com/parse-community/parse-server/issues/7692)) ([573558d](https://github.com/parse-community/parse-server/commit/573558d3adcbcc6222c92003829867e1a73eef94))
|
||||
* package.json & package-lock.json to reduce vulnerabilities ([#7823](https://github.com/parse-community/parse-server/issues/7823)) ([5ca2288](https://github.com/parse-community/parse-server/commit/5ca228882332b65f3ac05407e6e4da1ee3ef3749))
|
||||
* schema cache not cleared in some cases ([#7678](https://github.com/parse-community/parse-server/issues/7678)) ([5af6e5d](https://github.com/parse-community/parse-server/commit/5af6e5dfaa129b1a350afcba4fb381b21c4cc35d))
|
||||
* security upgrade follow-redirects from 1.14.6 to 1.14.7 ([#7769](https://github.com/parse-community/parse-server/issues/7769)) ([8f5a861](https://github.com/parse-community/parse-server/commit/8f5a8618cfa7ed9a2a239a095abffa8f3fd8d31a))
|
||||
* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7801](https://github.com/parse-community/parse-server/issues/7801)) ([70088a9](https://github.com/parse-community/parse-server/commit/70088a95a78393da2a4ac68be81e63107747626a))
|
||||
* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7844](https://github.com/parse-community/parse-server/issues/7844)) ([e569f40](https://github.com/parse-community/parse-server/commit/e569f402b1fd8648fb0d1523b71b2a03273902a5))
|
||||
* server crash using GraphQL due to missing @apollo/client peer dependency ([#7787](https://github.com/parse-community/parse-server/issues/7787)) ([08089d6](https://github.com/parse-community/parse-server/commit/08089d6fcbb215412448ce7d92b21b9fe6c929f2))
|
||||
* unable to use objectId size higher than 19 on GraphQL API ([#7627](https://github.com/parse-community/parse-server/issues/7627)) ([ed86c80](https://github.com/parse-community/parse-server/commit/ed86c807721cc52a1a5a9dea0b768717eec269ed))
|
||||
* upgrade mime from 2.5.2 to 3.0.0 ([#7725](https://github.com/parse-community/parse-server/issues/7725)) ([f5ef98b](https://github.com/parse-community/parse-server/commit/f5ef98bde32083403c0e30a12162fcc1e52cac37))
|
||||
* upgrade parse from 3.3.1 to 3.4.0 ([#7723](https://github.com/parse-community/parse-server/issues/7723)) ([d4c1f47](https://github.com/parse-community/parse-server/commit/d4c1f473073764cb0570c633fc4a30669c2ce889))
|
||||
* upgrade winston from 3.5.0 to 3.5.1 ([#7820](https://github.com/parse-community/parse-server/issues/7820)) ([4af253d](https://github.com/parse-community/parse-server/commit/4af253d1f8654a6f57b5137ad310cdacadc922cc))
|
||||
|
||||
### Features
|
||||
|
||||
* add Cloud Code context to `ParseObject.fetch` ([#7779](https://github.com/parse-community/parse-server/issues/7779)) ([315290d](https://github.com/parse-community/parse-server/commit/315290d16110110938f80a6b779cc2d1db58c552))
|
||||
* add Idempotency to Postgres ([#7750](https://github.com/parse-community/parse-server/issues/7750)) ([0c3feaa](https://github.com/parse-community/parse-server/commit/0c3feaaa1751964c0db89f25674935c3354b1538))
|
||||
* add support for Node 16 ([#7707](https://github.com/parse-community/parse-server/issues/7707)) ([45cc58c](https://github.com/parse-community/parse-server/commit/45cc58c7e5e640a46c5d508019a3aa81242964b1))
|
||||
* bump required node engine to >=12.22.10 ([#7846](https://github.com/parse-community/parse-server/issues/7846)) ([5ace99d](https://github.com/parse-community/parse-server/commit/5ace99d542a11e422af46d9fd6b1d3d2513b34cf))
|
||||
* support `postgresql` protocol in database URI ([#7757](https://github.com/parse-community/parse-server/issues/7757)) ([caf4a23](https://github.com/parse-community/parse-server/commit/caf4a2341f554b28e3918c53e7e897a3ca47bf8b))
|
||||
* support relativeTime query constraint on Postgres ([#7747](https://github.com/parse-community/parse-server/issues/7747)) ([16b1b2a](https://github.com/parse-community/parse-server/commit/16b1b2a19714535ca805f2dbb3b561d8f6a519a7))
|
||||
* upgrade to MongoDB Node.js driver 4.x for MongoDB 5.0 support ([#7794](https://github.com/parse-community/parse-server/issues/7794)) ([f88aa2a](https://github.com/parse-community/parse-server/commit/f88aa2a62a533e5344d1c13dd38c5a0b283a480a))
|
||||
|
||||
### Reverts
|
||||
|
||||
* refactor: allow ES import for cloud string if package type is module ([b64640c](https://github.com/parse-community/parse-server/commit/b64640c5705f733798783e68d216e957044ef23c))
|
||||
* update node engine to 2.22.0 ([#7827](https://github.com/parse-community/parse-server/issues/7827)) ([f235412](https://github.com/parse-community/parse-server/commit/f235412c1b6c2b173b7531f285429ea7214b56a2))
|
||||
|
||||
### ⚠️ NOTABLE CHANGES
|
||||
|
||||
*The following changes would formally require a major version increment (Parse Server 6.0), but given their low relevance they are released as part of this minor version increment (Parse Server 5.1).*
|
||||
|
||||
* The MongoDB GridStore adapter has been removed. By default, Parse Server already uses GridFS, so if you do not manually use the GridStore adapter, you can ignore this change. Parse Server uses the GridFSBucket adapter instead of GridStore adapter by default since 2018. ([f88aa2a](f88aa2a))
|
||||
* Removes official Node 15 support which has already reached it End-of-Life date. ([45cc58c](45cc58c))
|
||||
|
||||
|
||||
# [5.0.0](https://github.com/parse-community/parse-server/compare/4.10.7...5.0.0) (2022-03-14)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
- Improved schema caching through database real-time hooks. Reduces DB queries, decreases Parse Query execution time and fixes a potential schema memory leak. If multiple Parse Server instances connect to the same DB (for example behind a load balancer), set the [Parse Server Option](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `databaseOptions.enableSchemaHooks: true` to enable this feature and keep the schema in sync across all instances. Failing to do so will cause a schema change to not propagate to other instances and re-syncing will only happen when these instances restart. The options `enableSingleSchemaCache` and `schemaCacheTTL` have been removed. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required. (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214)
|
||||
- Fix security vulnerability that allows remote code execution; as part of the fix a new security feature scans for sensitive keywords in request data to prevent JavaScript prototype pollution. If such a keyword is found, the request is rejected with HTTP response code `400` and Parse Error `105` (`INVALID_KEY_NAME`). By default these keywords are: `{_bsontype: "Code"}`, `constructor`, `__proto__`. If you are using any of these keywords in your request data, you can override the default keywords by setting the new Parse Server option `requestKeywordDenylist` to `[]` and specify your own keywords as needed. ([GHSA-p6h4-93qp-jhcm](https://github.com/advisories/GHSA-p6h4-93qp-jhcm)) ([#7843](https://github.com/parse-community/parse-server/issues/7843)) ([971adb5](https://github.com/parse-community/parse-server/commit/971adb54387b0ede31be05ca407d5f35b4575c83))
|
||||
- Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (dblythy, Manuel Trezza) [#7071](https://github.com/parse-community/parse-server/pull/7071)
|
||||
- Removed [parse-server-simple-mailgun-adapter](https://github.com/parse-community/parse-server-simple-mailgun-adapter) dependency; to continue using the adapter it has to be explicitly installed (Manuel Trezza) [#7321](https://github.com/parse-community/parse-server/pull/7321)
|
||||
- Remove support for MongoDB 3.6 which has reached its End-of-Life date and PostgreSQL 10 (Manuel Trezza) [#7315](https://github.com/parse-community/parse-server/pull/7315)
|
||||
- Remove support for Node 10 which has reached its End-of-Life date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314)
|
||||
- Bump required Node engine to >=12.22.10 ([#7848](https://github.com/parse-community/parse-server/issues/7848)) ([23a3488](https://github.com/parse-community/parse-server/commit/23a3488f15511fafbe0e1d7ff0ef8355f9cb0215))
|
||||
- Remove S3 Files Adapter from Parse Server, instead install separately as `@parse/s3-files-adapter` (Manuel Trezza) [#7324](https://github.com/parse-community/parse-server/pull/7324)
|
||||
- Remove Session field `restricted`; the field was a code artifact from a feature that never existed in Open Source Parse Server; if you have been using this field for custom purposes, consider that for new Parse Server installations the field does not exist anymore in the schema, and for existing installations the field default value `false` will not be set anymore when creating a new session (Manuel Trezza) [#7543](https://github.com/parse-community/parse-server/pull/7543)
|
||||
- To delete a field via the GraphQL API, the field value has to be set to `null`. Previously, setting a field value to `null` would save a null value in the database, which was not according to the [GraphQL specs](https://spec.graphql.org/June2018/#sec-Null-Value). To delete a file field use `file: null`, the previous way of using `file: { file: null }` has become obsolete. ([626fad2](626fad2))
|
||||
|
||||
### Notable Changes
|
||||
- Alphabetical ordered GraphQL API, improved GraphQL Schema cache system and fix GraphQL input reassign issue (Moumouls) [#7344](https://github.com/parse-community/parse-server/issues/7344)
|
||||
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)
|
||||
- EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#7128](https://github.com/parse-community/parse-server/pull/7128)
|
||||
- EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/pull/7231)
|
||||
- Added Deprecation Policy to govern the introduction of breaking changes in a phased pattern that is more predictable for developers (Manuel Trezza) [#7199](https://github.com/parse-community/parse-server/pull/7199)
|
||||
- Add REST API endpoint `/loginAs` to create session of any user with master key; allows to impersonate another user. (GormanFletcher) [#7406](https://github.com/parse-community/parse-server/pull/7406)
|
||||
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
|
||||
- Added Parse Server Configuration `enforcePrivateUsers`, which will remove public access by default on new Parse.Users (dblythy) [#7319](https://github.com/parse-community/parse-server/pull/7319)
|
||||
- add support for Postgres 14 ([#7644](https://github.com/parse-community/parse-server/issues/7644)) ([090350a](https://github.com/parse-community/parse-server/commit/090350a7a0fac945394ca1cb24b290316ef06aa7))
|
||||
- add user-defined schema and migrations ([#7418](https://github.com/parse-community/parse-server/issues/7418)) ([25d5c30](https://github.com/parse-community/parse-server/commit/25d5c30be2111be332eb779eb0697774a17da7af))
|
||||
- setting a field to null does not delete it via GraphQL API ([#7649](https://github.com/parse-community/parse-server/issues/7649)) ([626fad2](https://github.com/parse-community/parse-server/commit/626fad2e71017dcc62196c487de5f908fa43000b))
|
||||
- combined `and` query with relational query condition returns incorrect results ([#7593](https://github.com/parse-community/parse-server/issues/7593)) ([174886e](https://github.com/parse-community/parse-server/commit/174886e385e091c6bbd4a84891ef95f80b50d05c))
|
||||
- node engine range has no upper limit to exclude incompatible node versions ([#7693](https://github.com/parse-community/parse-server/issues/7693)) ([6a54dac](https://github.com/parse-community/parse-server/commit/6a54dac24d9fb63a44f311b8d414f4aa64140f32))
|
||||
- unable to use objectId size higher than 19 on GraphQL API ([#7722](https://github.com/parse-community/parse-server/issues/7722)) ([8ee0445](https://github.com/parse-community/parse-server/commit/8ee0445c0aeeb88dff2559b46ade408071d22143))
|
||||
- schema cache not cleared in some cases ([#7771](https://github.com/parse-community/parse-server/issues/7771)) ([3b92fa1](https://github.com/parse-community/parse-server/commit/3b92fa1ca9e8889127a32eba913d68309397ca2c))
|
||||
|
||||
### Other Changes
|
||||
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)
|
||||
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
|
||||
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
|
||||
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)
|
||||
- Added convenience method `Parse.Cloud.sendEmail(...)` to send email via email adapter in Cloud Code (dblythy) [#7089](https://github.com/parse-community/parse-server/pull/7089)
|
||||
- LiveQuery support for $and, $nor, $containedBy, $geoWithin, $geoIntersects queries (dplewis) [#7113](https://github.com/parse-community/parse-server/pull/7113)
|
||||
- Supporting patterns in LiveQuery server's config parameter `classNames` (Nes-si) [#7131](https://github.com/parse-community/parse-server/pull/7131)
|
||||
- Added `requireAnyUserRoles` and `requireAllUserRoles` for Parse Cloud validator (dblythy) [#7097](https://github.com/parse-community/parse-server/pull/7097)
|
||||
- Support Facebook Limited Login (miguel-s) [#7219](https://github.com/parse-community/parse-server/pull/7219)
|
||||
- Removed Stage name check on aggregate pipelines (BRETT71) [#7237](https://github.com/parse-community/parse-server/pull/7237)
|
||||
- Retry transactions on MongoDB when it fails due to transient error (Antonio Davi Macedo Coelho de Castro) [#7187](https://github.com/parse-community/parse-server/pull/7187)
|
||||
- Bump tests to use Mongo 4.4.4 (Antonio Davi Macedo Coelho de Castro) [#7184](https://github.com/parse-community/parse-server/pull/7184)
|
||||
- Added new account lockout policy option `accountLockout.unlockOnPasswordReset` to automatically unlock account on password reset (Manuel Trezza) [#7146](https://github.com/parse-community/parse-server/pull/7146)
|
||||
- Test Parse Server continuously against all recent MongoDB versions that have not reached their end-of-life support date, added MongoDB compatibility table to Parse Server docs (Manuel Trezza) [#7161](https://github.com/parse-community/parse-server/pull/7161)
|
||||
- Test Parse Server continuously against all recent Node.js versions that have not reached their end-of-life support date, added Node.js compatibility table to Parse Server docs (Manuel Trezza) [7161](https://github.com/parse-community/parse-server/pull/7177)
|
||||
- Throw error on invalid Cloud Function validation configuration (dblythy) [#7154](https://github.com/parse-community/parse-server/pull/7154)
|
||||
- Allow Cloud Validator `options` to be async (dblythy) [#7155](https://github.com/parse-community/parse-server/pull/7155)
|
||||
- Optimize queries on classes with pointer permissions (Pedro Diaz) [#7061](https://github.com/parse-community/parse-server/pull/7061)
|
||||
- Test Parse Server continuously against all relevant Postgres versions (minor versions), added Postgres compatibility table to Parse Server docs (Corey Baker) [#7176](https://github.com/parse-community/parse-server/pull/7176)
|
||||
- Randomize test suite (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- Improve data consistency in Push and Job Status update (Diamond Lewis) [#7267](https://github.com/parse-community/parse-server/pull/7267)
|
||||
- Excluding keys that have trailing edges.node when performing GraphQL resolver (Chris Bland) [#7273](https://github.com/parse-community/parse-server/pull/7273)
|
||||
- Added centralized feature deprecation with standardized warning logs (Manuel Trezza) [#7303](https://github.com/parse-community/parse-server/pull/7303)
|
||||
- Use Node.js 15.13.0 in CI (Olle Jonsson) [#7312](https://github.com/parse-community/parse-server/pull/7312)
|
||||
- Fix file upload issue for S3 compatible storage (Linode, DigitalOcean) by avoiding empty tags property when creating a file (Ali Oguzhan Yildiz) [#7300](https://github.com/parse-community/parse-server/pull/7300)
|
||||
- Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332)
|
||||
- Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333)
|
||||
- Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341)
|
||||
- Fix select and excludeKey queries to properly accept JSON string arrays. Also allow nested fields in exclude (Corey Baker) [#7242](https://github.com/parse-community/parse-server/pull/7242)
|
||||
- Fix LiveQuery server crash when using $all query operator on a missing object key (Jason Posthuma) [#7421](https://github.com/parse-community/parse-server/pull/7421)
|
||||
- Added runtime deprecation warnings (Manuel Trezza) [#7451](https://github.com/parse-community/parse-server/pull/7451)
|
||||
- Add ability to pass context of an object via a header, X-Parse-Cloud-Context, for Cloud Code triggers. The header addition allows client SDK's to add context without injecting _context in the body of JSON objects (Corey Baker) [#7437](https://github.com/parse-community/parse-server/pull/7437)
|
||||
- Add CI check to add changelog entry (Manuel Trezza) [#7512](https://github.com/parse-community/parse-server/pull/7512)
|
||||
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
|
||||
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
|
||||
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
|
||||
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
|
||||
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
|
||||
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
|
||||
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)
|
||||
- refactor: deprecate `Parse.Cloud.httpRequest`; it is recommended to use a HTTP library instead. (Daniel Blyth) [#7595](https://github.com/parse-community/parse-server/pull/7595)
|
||||
- refactor: Modernize HTTPRequest tests (brandongregoryscott) [#7604](https://github.com/parse-community/parse-server/pull/7604)
|
||||
- Allow liveQuery on Session class (Daniel Blyth) [#7554](https://github.com/parse-community/parse-server/pull/7554)
|
||||
- security upgrade follow-redirects from 1.14.2 to 1.14.7 ([#7772](https://github.com/parse-community/parse-server/issues/7772)) ([4bd34b1](https://github.com/parse-community/parse-server/commit/4bd34b189bc9f5aa2e70b7e7c1a456e91b6de773))
|
||||
- security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7802](https://github.com/parse-community/parse-server/issues/7802)) ([7029b27](https://github.com/parse-community/parse-server/commit/7029b274ca87bc8058617f29865d683dc3b351a1))
|
||||
- Add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
|
||||
|
||||
## [4.10.7](https://github.com/parse-community/parse-server/compare/4.10.6...4.10.7) (2022-03-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security vulnerability that allows remote code execution ([GHSA-p6h4-93qp-jhcm](https://github.com/parse-community/parse-server/security/advisories/GHSA-p6h4-93qp-jhcm)) ([#7841](https://github.com/parse-community/parse-server/issues/7841)) ([886bfd7](https://github.com/parse-community/parse-server/commit/886bfd7cac69496e3f73d4bb536f0eec3cba0e4d))
|
||||
|
||||
Note that as part of the fix a new security feature scans for sensitive keywords in request data to prevent JavaScript prototype pollution. If such a keyword is found, the request is rejected with HTTP response code `400` and Parse Error `105` (`INVALID_KEY_NAME`). By default these keywords are: `{_bsontype: "Code"}`, `constructor`, `__proto__`. If you are using any of these keywords in your request data, you can override the default keywords by setting the new Parse Server option `requestKeywordDenylist` to `[]` and specify your own keywords as needed.
|
||||
|
||||
## [4.10.6](https://github.com/parse-community/parse-server/compare/4.10.5...4.10.6) (2022-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update graphql dependencies to work with Parse Dashboard ([#7658](https://github.com/parse-community/parse-server/issues/7658)) ([350ecde](https://github.com/parse-community/parse-server/commit/350ecdee590f1b9d721895b2c79306c01622c3fc))
|
||||
|
||||
## [4.10.5](https://github.com/parse-community/parse-server/compare/4.10.4...4.10.5) (2022-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade follow-redirects from 1.13.0 to 1.14.8 ([#7803](https://github.com/parse-community/parse-server/issues/7803)) ([611332e](https://github.com/parse-community/parse-server/commit/611332ea33831258efd3dd2f2c621c2e35fc95d3))
|
||||
|
||||
# [4.10.4](https://github.com/parse-community/parse-server/compare/4.10.3...4.10.4)
|
||||
|
||||
### Security Fixes
|
||||
|
||||
Generated
+64
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "5.0.0-alpha.27",
|
||||
"version": "5.2.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1991,6 +1991,66 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
|
||||
},
|
||||
"@saithodev/semantic-release-backmerge": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@saithodev/semantic-release-backmerge/-/semantic-release-backmerge-2.1.2.tgz",
|
||||
"integrity": "sha512-fNd8cmijjFIMp4GcdTAcug/7tr4k+8bAyvSsbLOnfyKCWyq42lg14vFZOryLiyLUAe8gpPlI7XzDPWyFTR5zug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@semantic-release/error": "^2.2.0 || ^3.0.0",
|
||||
"aggregate-error": "^3.1.0",
|
||||
"debug": "^4.3.2",
|
||||
"execa": "^5.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"semantic-release": ">=13.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.0",
|
||||
"human-signals": "^2.1.0",
|
||||
"is-stream": "^2.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^4.0.1",
|
||||
"onetime": "^5.1.2",
|
||||
"signal-exit": "^3.0.3",
|
||||
"strip-final-newline": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"dev": true
|
||||
},
|
||||
"human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@samverschueren/stream-to-observable": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
|
||||
@@ -10269,9 +10329,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist-options": {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "5.0.0-alpha.27",
|
||||
"version": "5.2.4",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -69,6 +69,7 @@
|
||||
"@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",
|
||||
"@semantic-release/git": "9.0.0",
|
||||
@@ -132,7 +133,7 @@
|
||||
"madge:circular": "node_modules/.bin/madge ./src --circular"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0 <17"
|
||||
"node": ">=12.22.10 <17"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "bin/parse-server"
|
||||
|
||||
@@ -88,6 +88,15 @@ async function config() {
|
||||
labels: ['type:ci'],
|
||||
releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
|
||||
}],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"branches": [
|
||||
{ from: "beta", to: "alpha" },
|
||||
{ from: "release", to: "beta" },
|
||||
]
|
||||
}
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -172,6 +172,20 @@ function parseDefaultValue(elt, value, t) {
|
||||
literalValue = t.arrayExpression(array.map((value) => {
|
||||
if (typeof value == 'string') {
|
||||
return t.stringLiteral(value);
|
||||
} else if (typeof value == 'number') {
|
||||
return t.numericLiteral(value);
|
||||
} else if (typeof value == 'object') {
|
||||
const object = parsers.objectParser(value);
|
||||
const props = Object.entries(object).map(([k, v]) => {
|
||||
if (typeof v == 'string') {
|
||||
return t.objectProperty(t.identifier(k), t.stringLiteral(v));
|
||||
} else if (typeof v == 'number') {
|
||||
return t.objectProperty(t.identifier(k), t.numericLiteral(v));
|
||||
} else if (typeof v == 'boolean') {
|
||||
return t.objectProperty(t.identifier(k), t.booleanLiteral(v));
|
||||
}
|
||||
});
|
||||
return t.objectExpression(props);
|
||||
} else {
|
||||
throw new Error('Unable to parse array');
|
||||
}
|
||||
|
||||
@@ -1652,8 +1652,41 @@ describe('apple signin auth adapter', () => {
|
||||
|
||||
describe('Apple Game Center Auth adapter', () => {
|
||||
const gcenter = require('../lib/Adapters/Auth/gcenter');
|
||||
|
||||
const fs = require('fs');
|
||||
const testCert = fs.readFileSync(__dirname + '/support/cert/game_center.pem');
|
||||
it('can load adapter', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
});
|
||||
it('validateAuthData should validate', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
// real token is used
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
@@ -1664,66 +1697,147 @@ describe('Apple Game Center Auth adapter', () => {
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'cloud.xtralife.gamecenterauth',
|
||||
};
|
||||
|
||||
try {
|
||||
await gcenter.validateAuthData(authData);
|
||||
} catch (e) {
|
||||
fail();
|
||||
}
|
||||
gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert;
|
||||
await gcenter.validateAuthData(authData);
|
||||
});
|
||||
|
||||
it('validateAuthData invalid signature id', async () => {
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
{}
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-6.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'cloud.xtralife.gamecenterauth',
|
||||
bundleId: 'com.example.com',
|
||||
};
|
||||
|
||||
try {
|
||||
await gcenter.validateAuthData(authData);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Apple Game Center - invalid signature');
|
||||
}
|
||||
});
|
||||
|
||||
it('validateAuthData invalid public key url', async () => {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'invalid.com',
|
||||
timestamp: 1565257031287,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'cloud.xtralife.gamecenterauth',
|
||||
};
|
||||
|
||||
try {
|
||||
await gcenter.validateAuthData(authData);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Apple Game Center - invalid publicKeyUrl: invalid.com');
|
||||
}
|
||||
await expectAsync(gcenter.validateAuthData(authData)).toBeRejectedWith(
|
||||
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Apple Game Center - invalid signature')
|
||||
);
|
||||
});
|
||||
|
||||
it('validateAuthData invalid public key http url', async () => {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'http://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'cloud.xtralife.gamecenterauth',
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const publicKeyUrls = [
|
||||
'example.com',
|
||||
'http://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
'https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg',
|
||||
'https://example.com/ \\.apple.com/public_key.cer',
|
||||
'https://example.com/ &.apple.com/public_key.cer',
|
||||
];
|
||||
await Promise.all(
|
||||
publicKeyUrls.map(publicKeyUrl =>
|
||||
expectAsync(
|
||||
gcenter.validateAuthData({
|
||||
id: 'G:1965586982',
|
||||
timestamp: 1565257031287,
|
||||
publicKeyUrl,
|
||||
signature: '1234',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'com.example.com',
|
||||
})
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.SCRIPT_FAILED,
|
||||
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
await gcenter.validateAuthData(authData);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Apple Game Center - invalid publicKeyUrl: http://static.gc.apple.com/public-key/gc-prod-4.cer');
|
||||
}
|
||||
it('should not validate Symantec Cert', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl:
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
expect(() =>
|
||||
gcenter.verifyPublicKeyIssuer(
|
||||
testCert,
|
||||
'https://static.gc.apple.com/public-key/gc-prod-4.cer'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('adapter should load default cert', async () => {
|
||||
const options = {
|
||||
gcenter: {},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
const previous = new Date();
|
||||
await adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
);
|
||||
|
||||
const duration = new Date().getTime() - previous.getTime();
|
||||
expect(duration).toEqual(0);
|
||||
});
|
||||
|
||||
it('adapter should throw', async () => {
|
||||
const options = {
|
||||
gcenter: {
|
||||
rootCertificateUrl: 'https://example.com',
|
||||
},
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
|
||||
'gcenter',
|
||||
options
|
||||
);
|
||||
await expectAsync(
|
||||
adapter.validateAppId(
|
||||
appIds,
|
||||
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
|
||||
providerOptions
|
||||
)
|
||||
).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+25
-22
@@ -93,28 +93,31 @@ describe('Idempotency', () => {
|
||||
expect(counter).toBe(2);
|
||||
});
|
||||
|
||||
it_only_db('postgres')('should delete request entry when postgress ttl function is called', async () => {
|
||||
const client = Config.get(Parse.applicationId).database.adapter._client;
|
||||
let counter = 0;
|
||||
Parse.Cloud.define('myFunction', () => {
|
||||
counter++;
|
||||
});
|
||||
const params = {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/functions/myFunction',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Master-Key': Parse.masterKey,
|
||||
'X-Parse-Request-Id': 'abc-123',
|
||||
},
|
||||
};
|
||||
await expectAsync(request(params)).toBeResolved();
|
||||
await expectAsync(request(params)).toBeRejected();
|
||||
await new Promise(resolve => setTimeout(resolve, maxTimeOut));
|
||||
await client.one('SELECT idempotency_delete_expired_records()');
|
||||
await expectAsync(request(params)).toBeResolved();
|
||||
expect(counter).toBe(2);
|
||||
});
|
||||
it_only_db('postgres')(
|
||||
'should delete request entry when postgress ttl function is called',
|
||||
async () => {
|
||||
const client = Config.get(Parse.applicationId).database.adapter._client;
|
||||
let counter = 0;
|
||||
Parse.Cloud.define('myFunction', () => {
|
||||
counter++;
|
||||
});
|
||||
const params = {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/functions/myFunction',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Master-Key': Parse.masterKey,
|
||||
'X-Parse-Request-Id': 'abc-123',
|
||||
},
|
||||
};
|
||||
await expectAsync(request(params)).toBeResolved();
|
||||
await expectAsync(request(params)).toBeRejected();
|
||||
await new Promise(resolve => setTimeout(resolve, maxTimeOut));
|
||||
await client.one('SELECT idempotency_delete_expired_records()');
|
||||
await expectAsync(request(params)).toBeResolved();
|
||||
expect(counter).toBe(2);
|
||||
}
|
||||
);
|
||||
|
||||
it('should enforce idempotency for cloud code jobs', async () => {
|
||||
let counter = 0;
|
||||
|
||||
@@ -654,6 +654,44 @@ describe('Parse.File testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getting files', () => {
|
||||
it('does not crash on file request with invalid app ID', async () => {
|
||||
const res1 = await request({
|
||||
url: 'http://localhost:8378/1/files/invalid-id/invalid-file.txt',
|
||||
}).catch(e => e);
|
||||
expect(res1.status).toBe(403);
|
||||
expect(res1.data).toEqual({ code: 119, error: 'Invalid application ID.' });
|
||||
// Ensure server did not crash
|
||||
const res2 = await request({ url: 'http://localhost:8378/1/health' });
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.data).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
it('does not crash on file request with invalid path', async () => {
|
||||
const res1 = await request({
|
||||
url: 'http://localhost:8378/1/files/invalid-id//invalid-path/%20/invalid-file.txt',
|
||||
}).catch(e => e);
|
||||
expect(res1.status).toBe(403);
|
||||
expect(res1.data).toEqual({ error: 'unauthorized' });
|
||||
// Ensure server did not crash
|
||||
const res2 = await request({ url: 'http://localhost:8378/1/health' });
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.data).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
it('does not crash on file metadata request with invalid app ID', async () => {
|
||||
const res1 = await request({
|
||||
url: `http://localhost:8378/1/files/invalid-id/metadata/invalid-file.txt`,
|
||||
});
|
||||
expect(res1.status).toBe(200);
|
||||
expect(res1.data).toEqual({});
|
||||
// Ensure server did not crash
|
||||
const res2 = await request({ url: 'http://localhost:8378/1/health' });
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.data).toEqual({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('Gridstore Range tests', () => {
|
||||
it('supports range requests', done => {
|
||||
const headers = {
|
||||
|
||||
@@ -319,6 +319,41 @@ describe('ParseLiveQuery', function () {
|
||||
await object.save();
|
||||
});
|
||||
|
||||
it('can log on afterLiveQueryEvent throw', async () => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
const object = new TestObject();
|
||||
await object.save();
|
||||
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callFake(() => {});
|
||||
|
||||
let session = undefined;
|
||||
Parse.Cloud.afterLiveQueryEvent('TestObject', ({ sessionToken }) => {
|
||||
session = sessionToken;
|
||||
/* eslint-disable no-undef */
|
||||
foo.bar();
|
||||
/* eslint-enable no-undef */
|
||||
});
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('objectId', object.id);
|
||||
const subscription = await query.subscribe();
|
||||
object.set({ foo: 'bar' });
|
||||
await object.save();
|
||||
await new Promise(resolve => subscription.on('error', resolve));
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Failed running afterLiveQueryEvent on class TestObject for event update with session ${session} with:\n Error: {"message":"foo is not defined","code":141}`
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle afterEvent sendEvent to false', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
@@ -566,6 +601,33 @@ describe('ParseLiveQuery', function () {
|
||||
await query.subscribe();
|
||||
});
|
||||
|
||||
it('can log on beforeConnect throw', async () => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callFake(() => {});
|
||||
let token = undefined;
|
||||
Parse.Cloud.beforeConnect(({ sessionToken }) => {
|
||||
token = sessionToken;
|
||||
/* eslint-disable no-undef */
|
||||
foo.bar();
|
||||
/* eslint-enable no-undef */
|
||||
});
|
||||
new Parse.Query(TestObject).subscribe();
|
||||
await new Promise(resolve => Parse.LiveQuery.on('error', resolve));
|
||||
Parse.LiveQuery.removeAllListeners('error');
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Failed running beforeConnect for session ${token} with:\n Error: {"message":"foo is not defined","code":141}`
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle beforeSubscribe error', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
@@ -594,6 +656,34 @@ describe('ParseLiveQuery', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can log on beforeSubscribe error', async () => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callFake(() => {});
|
||||
|
||||
Parse.Cloud.beforeSubscribe(TestObject, () => {
|
||||
/* eslint-disable no-undef */
|
||||
foo.bar();
|
||||
/* eslint-enable no-undef */
|
||||
});
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
const subscription = await query.subscribe();
|
||||
await new Promise(resolve => subscription.on('error', resolve));
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Failed running beforeSubscribe on TestObject for session undefined with:\n Error: {"message":"foo is not defined","code":141}`
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle mutate beforeSubscribe query', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
@@ -976,6 +1066,52 @@ describe('ParseLiveQuery', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('should strip out protected fields', async () => {
|
||||
await reconfigureServer({
|
||||
liveQuery: { classNames: ['Test'] },
|
||||
startLiveQueryServer: true,
|
||||
});
|
||||
const obj1 = new Parse.Object('Test');
|
||||
obj1.set('foo', 'foo');
|
||||
obj1.set('bar', 'bar');
|
||||
obj1.set('qux', 'qux');
|
||||
await obj1.save();
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const schemaController = await config.database.loadSchema();
|
||||
await schemaController.updateClass(
|
||||
'Test',
|
||||
{},
|
||||
{
|
||||
get: { '*': true },
|
||||
find: { '*': true },
|
||||
update: { '*': true },
|
||||
protectedFields: {
|
||||
'*': ['foo'],
|
||||
},
|
||||
}
|
||||
);
|
||||
const object = await obj1.fetch();
|
||||
expect(object.get('foo')).toBe(undefined);
|
||||
expect(object.get('bar')).toBeDefined();
|
||||
expect(object.get('qux')).toBeDefined();
|
||||
|
||||
const subscription = await new Parse.Query('Test').subscribe();
|
||||
await Promise.all([
|
||||
new Promise(resolve => {
|
||||
subscription.on('update', (obj, original) => {
|
||||
expect(obj.get('foo')).toBe(undefined);
|
||||
expect(obj.get('bar')).toBeDefined();
|
||||
expect(obj.get('qux')).toBeDefined();
|
||||
expect(original.get('foo')).toBe(undefined);
|
||||
expect(original.get('bar')).toBeDefined();
|
||||
expect(original.get('qux')).toBeDefined();
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
obj1.save({ foo: 'abc' }),
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(async function (done) {
|
||||
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
|
||||
client.close();
|
||||
|
||||
@@ -4777,7 +4777,7 @@ describe('Parse.Query testing', () => {
|
||||
ttl: new Date(now - 2 * 24 * 60 * 60 * 1000), // 2 days ago
|
||||
});
|
||||
|
||||
await Parse.Object.saveAll([obj1, obj2])
|
||||
await Parse.Object.saveAll([obj1, obj2]);
|
||||
const q1 = new Parse.Query('MyCustomObject');
|
||||
q1.greaterThan('ttl', { $relativeTime: 'in 1 day' });
|
||||
const results1 = await q1.find({ useMasterKey: true });
|
||||
@@ -4825,8 +4825,8 @@ describe('Parse.Query testing', () => {
|
||||
q.greaterThan('ttl', { $relativeTime: '-12 bananas ago' });
|
||||
try {
|
||||
await q.find({ useMasterKey: true });
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe(Parse.Error.INVALID_JSON);
|
||||
}
|
||||
});
|
||||
@@ -4842,8 +4842,8 @@ describe('Parse.Query testing', () => {
|
||||
q.greaterThan('nonDateField', { $relativeTime: '1 day ago' });
|
||||
try {
|
||||
await q.find({ useMasterKey: true });
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe(Parse.Error.INVALID_JSON);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -186,10 +186,7 @@ describe('ParseServerRESTController', () => {
|
||||
databaseAdapter.createObject.calls.argsFor(i + 1)[3]
|
||||
);
|
||||
}
|
||||
expect(results.map(result => result.get('key')).sort()).toEqual([
|
||||
'value1',
|
||||
'value2',
|
||||
]);
|
||||
expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']);
|
||||
});
|
||||
|
||||
it('should not save anything when one operation fails in a transaction', async () => {
|
||||
|
||||
@@ -24,7 +24,6 @@ const GameScore = Parse.Object.extend({
|
||||
});
|
||||
|
||||
describe_only_db('postgres')('Postgres database init options', () => {
|
||||
|
||||
it('should create server with public schema databaseOptions', async () => {
|
||||
const adapter = new PostgresStorageAdapter({
|
||||
uri: postgresURI,
|
||||
@@ -71,8 +70,8 @@ describe_only_db('postgres')('Postgres database init options', () => {
|
||||
await reconfigureServer({
|
||||
databaseAdapter: adapter,
|
||||
});
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -179,14 +179,14 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
|
||||
{
|
||||
createdAt: {
|
||||
$eq: {
|
||||
$relativeTime: '12 days ago'
|
||||
}
|
||||
}
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ }
|
||||
{}
|
||||
);
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe(Parse.Error.INVALID_JSON);
|
||||
}
|
||||
await dropTable(client, tableName);
|
||||
@@ -222,14 +222,14 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
|
||||
{
|
||||
createdAt: {
|
||||
$ne: {
|
||||
$relativeTime: '12 days ago'
|
||||
}
|
||||
}
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ }
|
||||
{}
|
||||
);
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe(Parse.Error.INVALID_JSON);
|
||||
}
|
||||
await dropTable(client, tableName);
|
||||
@@ -265,14 +265,14 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
|
||||
{
|
||||
createdAt: {
|
||||
$exists: {
|
||||
$relativeTime: '12 days ago'
|
||||
}
|
||||
}
|
||||
$relativeTime: '12 days ago',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ }
|
||||
{}
|
||||
);
|
||||
fail("Should have thrown error");
|
||||
} catch(error) {
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.code).toBe(Parse.Error.INVALID_JSON);
|
||||
}
|
||||
await dropTable(client, tableName);
|
||||
@@ -563,9 +563,10 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
|
||||
await reconfigureServer();
|
||||
const adapter = Config.get('test').database.adapter;
|
||||
const client = adapter._client;
|
||||
const qs = "SELECT format('%I.%I(%s)', ns.nspname, p.proname, oidvectortypes(p.proargtypes)) FROM pg_proc p INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid) WHERE p.proname = 'idempotency_delete_expired_records'";
|
||||
const qs =
|
||||
"SELECT format('%I.%I(%s)', ns.nspname, p.proname, oidvectortypes(p.proargtypes)) FROM pg_proc p INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid) WHERE p.proname = 'idempotency_delete_expired_records'";
|
||||
const foundFunction = await client.one(qs);
|
||||
expect(foundFunction.format).toBe("public.idempotency_delete_expired_records()");
|
||||
expect(foundFunction.format).toBe('public.idempotency_delete_expired_records()');
|
||||
await adapter.deleteIdempotencyFunction();
|
||||
await client.none(qs);
|
||||
});
|
||||
|
||||
+1
-4
@@ -267,10 +267,7 @@ describe('batch', () => {
|
||||
databaseAdapter.createObject.calls.argsFor(i + 1)[3]
|
||||
);
|
||||
}
|
||||
expect(results.map(result => result.get('key')).sort()).toEqual([
|
||||
'value1',
|
||||
'value2',
|
||||
]);
|
||||
expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']);
|
||||
});
|
||||
|
||||
it('should not save anything when one operation fails in a transaction', async () => {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEvDCCA6SgAwIBAgIQXRHxNXkw1L9z5/3EZ/T/hDANBgkqhkiG9w0BAQsFADB/
|
||||
MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAd
|
||||
BgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVj
|
||||
IENsYXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTAeFw0xODA5MTcwMDAwMDBa
|
||||
Fw0xOTA5MTcyMzU5NTlaMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y
|
||||
bmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8xFDASBgNVBAoMC0FwcGxlLCBJbmMuMQ8w
|
||||
DQYDVQQLDAZHQyBTUkUxFDASBgNVBAMMC0FwcGxlLCBJbmMuMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA06fwIi8fgKrTQu7cBcFkJVF6+Tqvkg7MKJTM
|
||||
IOYPPQtPF3AZYPsbUoRKAD7/JXrxxOSVJ7vU1mP77tYG8TcUteZ3sAwvt2dkRbm7
|
||||
ZO6DcmSggv1Dg4k3goNw4GYyCY4Z2/8JSmsQ80Iv/UOOwynpBziEeZmJ4uck6zlA
|
||||
17cDkH48LBpKylaqthym5bFs9gj11pto7mvyb5BTcVuohwi6qosvbs/4VGbC2Nsz
|
||||
ie416nUZfv+xxoXH995gxR2mw5cDdeCew7pSKxEhvYjT2nVdQF0q/hnPMFnOaEyT
|
||||
q79n3gwFXyt0dy8eP6KBF7EW9J6b7ubu/j7h+tQfxPM+gTXOBQIDAQABo4IBPjCC
|
||||
ATowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||
AwMwYQYDVR0gBFowWDBWBgZngQwBBAEwTDAjBggrBgEFBQcCARYXaHR0cHM6Ly9k
|
||||
LnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUHAgIwGQwXaHR0cHM6Ly9kLnN5bWNiLmNv
|
||||
bS9ycGEwHwYDVR0jBBgwFoAUljtT8Hkzl699g+8uK8zKt4YecmYwKwYDVR0fBCQw
|
||||
IjAgoB6gHIYaaHR0cDovL3N2LnN5bWNiLmNvbS9zdi5jcmwwVwYIKwYBBQUHAQEE
|
||||
SzBJMB8GCCsGAQUFBzABhhNodHRwOi8vc3Yuc3ltY2QuY29tMCYGCCsGAQUFBzAC
|
||||
hhpodHRwOi8vc3Yuc3ltY2IuY29tL3N2LmNydDANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||
I/j/PcCNPebSAGrcqSFBSa2mmbusOX01eVBg8X0G/z8Z+ZWUfGFzDG0GQf89MPxV
|
||||
woec+nZuqui7o9Bg8s8JbHV0TC52X14CbTj9w/qBF748WbH9gAaTkrJYPm+MlNhu
|
||||
tjEuQdNl/YXVMvQW4O8UMHTi09GyJQ0NC4q92Wxvx1m/qzjvTLvrXHGQ9pEHhPyz
|
||||
vfBLxQkWpNoCNKU7UeESyH06XOrGc9MsII9deeKsDJp9a0jtx+pP4MFVtFME9SSQ
|
||||
tMBs0It7WwEf7qcRLpialxKwY2EzQ9g4WnANHqo18PrDBE10TFpZPzUh7JhMViVr
|
||||
EEbl0YdElmF8Hlamah/yNw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,297 @@
|
||||
const request = require('../lib/request');
|
||||
|
||||
describe('Vulnerabilities', () => {
|
||||
describe('Object prototype pollution', () => {
|
||||
it('denies object prototype to be polluted with keyword "constructor"', async () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const response = await request({
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/PP',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
constructor: {
|
||||
prototype: {
|
||||
dummy: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe('Prohibited keyword in request data: {"key":"constructor"}.');
|
||||
expect(Object.prototype.dummy).toBeUndefined();
|
||||
});
|
||||
|
||||
it('denies object prototype to be polluted with keypath string "constructor"', async () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const objResponse = await request({
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/PP',
|
||||
body: JSON.stringify({
|
||||
obj: {},
|
||||
}),
|
||||
}).catch(e => e);
|
||||
const pollResponse = await request({
|
||||
headers: headers,
|
||||
method: 'PUT',
|
||||
url: `http://localhost:8378/1/classes/PP/${objResponse.data.objectId}`,
|
||||
body: JSON.stringify({
|
||||
'obj.constructor.prototype.dummy': {
|
||||
__op: 'Increment',
|
||||
amount: 1,
|
||||
},
|
||||
}),
|
||||
}).catch(e => e);
|
||||
expect(Object.prototype.dummy).toBeUndefined();
|
||||
expect(pollResponse.status).toBe(400);
|
||||
const text = JSON.parse(pollResponse.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe('Prohibited keyword in request data: {"key":"constructor"}.');
|
||||
expect(Object.prototype.dummy).toBeUndefined();
|
||||
});
|
||||
|
||||
it('denies object prototype to be polluted with keyword "__proto__"', async () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const response = await request({
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/PP',
|
||||
body: JSON.stringify({ 'obj.__proto__.dummy': 0 }),
|
||||
}).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe('Prohibited keyword in request data: {"key":"__proto__"}.');
|
||||
expect(Object.prototype.dummy).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Request denylist', () => {
|
||||
it('denies BSON type code data in write request by default', async () => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
_bsontype: 'Code',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe(
|
||||
'Prohibited keyword in request data: {"key":"_bsontype","value":"Code"}.'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows BSON type code data in write request with custom denylist', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
_bsontype: 'Code',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(201);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.objectId).toBeDefined();
|
||||
});
|
||||
|
||||
it('denies write request with custom denylist of key/value', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
aKey: 'aValue321',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe(
|
||||
'Prohibited keyword in request data: {"key":"a[K]ey","value":"aValue[123]*"}.'
|
||||
);
|
||||
});
|
||||
|
||||
it('denies write request with custom denylist of nested key/value', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
nested: {
|
||||
aKey: 'aValue321',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe(
|
||||
'Prohibited keyword in request data: {"key":"a[K]ey","value":"aValue[123]*"}.'
|
||||
);
|
||||
});
|
||||
|
||||
it('denies write request with custom denylist of key/value in array', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: [
|
||||
{
|
||||
aKey: 'aValue321',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe(
|
||||
'Prohibited keyword in request data: {"key":"a[K]ey","value":"aValue[123]*"}.'
|
||||
);
|
||||
});
|
||||
|
||||
it('denies write request with custom denylist of key', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ key: 'a[K]ey' }],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
aKey: 'aValue321',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe('Prohibited keyword in request data: {"key":"a[K]ey"}.');
|
||||
});
|
||||
|
||||
it('denies write request with custom denylist of value', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ value: 'aValue[123]*' }],
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const params = {
|
||||
headers: headers,
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/RCE',
|
||||
body: JSON.stringify({
|
||||
obj: {
|
||||
aKey: 'aValue321',
|
||||
code: 'delete Object.prototype.evalFunctions',
|
||||
},
|
||||
}),
|
||||
};
|
||||
const response = await request(params).catch(e => e);
|
||||
expect(response.status).toBe(400);
|
||||
const text = JSON.parse(response.text);
|
||||
expect(text.code).toBe(Parse.Error.INVALID_KEY_NAME);
|
||||
expect(text.error).toBe('Prohibited keyword in request data: {"value":"aValue[123]*"}.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ignore non-matches', () => {
|
||||
it('ignores write request that contains only fraction of denied keyword', async () => {
|
||||
await reconfigureServer({
|
||||
requestKeywordDenylist: [{ key: 'abc' }],
|
||||
});
|
||||
// Initially saving an object executes the keyword detection in RestWrite.js
|
||||
const obj = new TestObject({ a: { b: { c: 0 } } });
|
||||
await expectAsync(obj.save()).toBeResolved();
|
||||
// Modifying a nested key executes the keyword detection in DatabaseController.js
|
||||
obj.increment('a.b.c');
|
||||
await expectAsync(obj.save()).toBeResolved();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,21 +14,15 @@ const authData = {
|
||||
const { Parse } = require('parse/node');
|
||||
const crypto = require('crypto');
|
||||
const https = require('https');
|
||||
|
||||
const { pki } = require('node-forge');
|
||||
const ca = { cert: null, url: null };
|
||||
const cache = {}; // (publicKey -> cert) cache
|
||||
|
||||
function verifyPublicKeyUrl(publicKeyUrl) {
|
||||
try {
|
||||
const parsedUrl = new URL(publicKeyUrl);
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
const hostnameParts = parsedUrl.hostname.split('.');
|
||||
const length = hostnameParts.length;
|
||||
const domainParts = hostnameParts.slice(length - 2, length);
|
||||
const domain = domainParts.join('.');
|
||||
return domain === 'apple.com';
|
||||
} catch(error) {
|
||||
const regex = /^https:\/\/(?:[-_A-Za-z0-9]+\.){0,}apple\.com\/.*\.cer$/;
|
||||
return regex.test(publicKeyUrl);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +37,7 @@ function convertX509CertToPEM(X509Cert) {
|
||||
return pemPreFix + certBody + pemPostFix;
|
||||
}
|
||||
|
||||
function getAppleCertificate(publicKeyUrl) {
|
||||
async function getAppleCertificate(publicKeyUrl) {
|
||||
if (!verifyPublicKeyUrl(publicKeyUrl)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
@@ -53,26 +47,59 @@ function getAppleCertificate(publicKeyUrl) {
|
||||
if (cache[publicKeyUrl]) {
|
||||
return cache[publicKeyUrl];
|
||||
}
|
||||
const url = new URL(publicKeyUrl);
|
||||
const headOptions = {
|
||||
hostname: url.hostname,
|
||||
path: url.pathname,
|
||||
method: 'HEAD',
|
||||
};
|
||||
const cert_headers = await new Promise((resolve, reject) =>
|
||||
https.get(headOptions, res => resolve(res.headers)).on('error', reject)
|
||||
);
|
||||
const validContentTypes = ['application/x-x509-ca-cert', 'application/pkix-cert'];
|
||||
if (
|
||||
!validContentTypes.includes(cert_headers['content-type']) ||
|
||||
cert_headers['content-length'] == null ||
|
||||
cert_headers['content-length'] > 10000
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
|
||||
);
|
||||
}
|
||||
const { certificate, headers } = await getCertificate(publicKeyUrl);
|
||||
if (headers['cache-control']) {
|
||||
const expire = headers['cache-control'].match(/max-age=([0-9]+)/);
|
||||
if (expire) {
|
||||
cache[publicKeyUrl] = certificate;
|
||||
// we'll expire the cache entry later, as per max-age
|
||||
setTimeout(() => {
|
||||
delete cache[publicKeyUrl];
|
||||
}, parseInt(expire[1], 10) * 1000);
|
||||
}
|
||||
}
|
||||
return verifyPublicKeyIssuer(certificate, publicKeyUrl);
|
||||
}
|
||||
|
||||
function getCertificate(url, buffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(publicKeyUrl, res => {
|
||||
let data = '';
|
||||
.get(url, res => {
|
||||
const data = [];
|
||||
res.on('data', chunk => {
|
||||
data += chunk.toString('base64');
|
||||
data.push(chunk);
|
||||
});
|
||||
res.on('end', () => {
|
||||
const cert = convertX509CertToPEM(data);
|
||||
if (res.headers['cache-control']) {
|
||||
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
|
||||
if (expire) {
|
||||
cache[publicKeyUrl] = cert;
|
||||
// we'll expire the cache entry later, as per max-age
|
||||
setTimeout(() => {
|
||||
delete cache[publicKeyUrl];
|
||||
}, parseInt(expire[1], 10) * 1000);
|
||||
}
|
||||
if (buffer) {
|
||||
resolve({ certificate: Buffer.concat(data), headers: res.headers });
|
||||
return;
|
||||
}
|
||||
resolve(cert);
|
||||
let cert = '';
|
||||
for (const chunk of data) {
|
||||
cert += chunk.toString('base64');
|
||||
}
|
||||
const certificate = convertX509CertToPEM(cert);
|
||||
resolve({ certificate, headers: res.headers });
|
||||
});
|
||||
})
|
||||
.on('error', reject);
|
||||
@@ -103,6 +130,30 @@ function verifySignature(publicKey, authData) {
|
||||
}
|
||||
}
|
||||
|
||||
function verifyPublicKeyIssuer(cert, publicKeyUrl) {
|
||||
const publicKeyCert = pki.certificateFromPem(cert);
|
||||
if (!ca.cert) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (!ca.cert.verify(publicKeyCert)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
|
||||
);
|
||||
}
|
||||
return cert;
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills if this user id is valid.
|
||||
async function validateAuthData(authData) {
|
||||
if (!authData.id) {
|
||||
@@ -114,11 +165,31 @@ async function validateAuthData(authData) {
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills if this app id is valid.
|
||||
function validateAppId() {
|
||||
return Promise.resolve();
|
||||
async function validateAppId(appIds, authData, options = {}) {
|
||||
if (!options.rootCertificateUrl) {
|
||||
options.rootCertificateUrl =
|
||||
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem';
|
||||
}
|
||||
if (ca.url === options.rootCertificateUrl) {
|
||||
return;
|
||||
}
|
||||
const { certificate, headers } = await getCertificate(options.rootCertificateUrl, true);
|
||||
if (
|
||||
headers['content-type'] !== 'application/x-pem-file' ||
|
||||
headers['content-length'] == null ||
|
||||
headers['content-length'] > 10000
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'
|
||||
);
|
||||
}
|
||||
ca.cert = pki.certificateFromPem(certificate);
|
||||
ca.url = options.rootCertificateUrl;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId,
|
||||
validateAuthData,
|
||||
cache,
|
||||
};
|
||||
|
||||
@@ -2440,55 +2440,48 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
? fieldNames.map((fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops`)
|
||||
: fieldNames.map((fieldName, index) => `$${index + 3}:name`);
|
||||
const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`;
|
||||
const setIdempotencyFunction = options.setIdempotencyFunction !== undefined ? options.setIdempotencyFunction : false;
|
||||
const setIdempotencyFunction =
|
||||
options.setIdempotencyFunction !== undefined ? options.setIdempotencyFunction : false;
|
||||
if (setIdempotencyFunction) {
|
||||
await this.ensureIdempotencyFunctionExists(options);
|
||||
}
|
||||
await conn.none(qs, [indexNameOptions.name, className, ...fieldNames])
|
||||
.catch(error => {
|
||||
if (
|
||||
error.code === PostgresDuplicateRelationError &&
|
||||
error.message.includes(indexNameOptions.name)
|
||||
) {
|
||||
// Index already exists. Ignore error.
|
||||
} else if (
|
||||
error.code === PostgresUniqueIndexViolationError &&
|
||||
error.message.includes(indexNameOptions.name)
|
||||
) {
|
||||
// Cast the error into the proper parse error
|
||||
throw new Parse.Error(
|
||||
Parse.Error.DUPLICATE_VALUE,
|
||||
'A duplicate value for a field with unique values was provided'
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
await conn.none(qs, [indexNameOptions.name, className, ...fieldNames]).catch(error => {
|
||||
if (
|
||||
error.code === PostgresDuplicateRelationError &&
|
||||
error.message.includes(indexNameOptions.name)
|
||||
) {
|
||||
// Index already exists. Ignore error.
|
||||
} else if (
|
||||
error.code === PostgresUniqueIndexViolationError &&
|
||||
error.message.includes(indexNameOptions.name)
|
||||
) {
|
||||
// Cast the error into the proper parse error
|
||||
throw new Parse.Error(
|
||||
Parse.Error.DUPLICATE_VALUE,
|
||||
'A duplicate value for a field with unique values was provided'
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async deleteIdempotencyFunction(
|
||||
options?: Object = {}
|
||||
): Promise<any> {
|
||||
async deleteIdempotencyFunction(options?: Object = {}): Promise<any> {
|
||||
const conn = options.conn !== undefined ? options.conn : this._client;
|
||||
const qs = 'DROP FUNCTION IF EXISTS idempotency_delete_expired_records()';
|
||||
return conn
|
||||
.none(qs)
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
return conn.none(qs).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async ensureIdempotencyFunctionExists(
|
||||
options?: Object = {}
|
||||
): Promise<any> {
|
||||
async ensureIdempotencyFunctionExists(options?: Object = {}): Promise<any> {
|
||||
const conn = options.conn !== undefined ? options.conn : this._client;
|
||||
const ttlOptions = options.ttl !== undefined ? `${options.ttl} seconds` : '60 seconds';
|
||||
const qs = 'CREATE OR REPLACE FUNCTION idempotency_delete_expired_records() RETURNS void LANGUAGE plpgsql AS $$ BEGIN DELETE FROM "_Idempotency" WHERE expire < NOW() - INTERVAL $1; END; $$;';
|
||||
return conn
|
||||
.none(qs, [ttlOptions])
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
const qs =
|
||||
'CREATE OR REPLACE FUNCTION idempotency_delete_expired_records() RETURNS void LANGUAGE plpgsql AS $$ BEGIN DELETE FROM "_Idempotency" WHERE expire < NOW() - INTERVAL $1; END; $$;';
|
||||
return conn.none(qs, [ttlOptions]).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -35,7 +35,7 @@ export class Config {
|
||||
config.applicationId = applicationId;
|
||||
Object.keys(cacheInfo).forEach(key => {
|
||||
if (key == 'databaseController') {
|
||||
config.database = new DatabaseController(cacheInfo.databaseController.adapter);
|
||||
config.database = new DatabaseController(cacheInfo.databaseController.adapter, config);
|
||||
} else {
|
||||
config[key] = cacheInfo[key];
|
||||
}
|
||||
@@ -78,6 +78,7 @@ export class Config {
|
||||
security,
|
||||
enforcePrivateUsers,
|
||||
schema,
|
||||
requestKeywordDenylist,
|
||||
}) {
|
||||
if (masterKey === readOnlyMasterKey) {
|
||||
throw new Error('masterKey and readOnlyMasterKey should be different');
|
||||
@@ -116,6 +117,15 @@ export class Config {
|
||||
this.validateSecurityOptions(security);
|
||||
this.validateSchemaOptions(schema);
|
||||
this.validateEnforcePrivateUsers(enforcePrivateUsers);
|
||||
this.validateRequestKeywordDenylist(requestKeywordDenylist);
|
||||
}
|
||||
|
||||
static validateRequestKeywordDenylist(requestKeywordDenylist) {
|
||||
if (requestKeywordDenylist === undefined) {
|
||||
requestKeywordDenylist = requestKeywordDenylist.default;
|
||||
} else if (!Array.isArray(requestKeywordDenylist)) {
|
||||
throw 'Parse Server option requestKeywordDenylist must be an array.';
|
||||
}
|
||||
}
|
||||
|
||||
static validateEnforcePrivateUsers(enforcePrivateUsers) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import intersect from 'intersect';
|
||||
// @flow-disable-next
|
||||
import deepcopy from 'deepcopy';
|
||||
import logger from '../logger';
|
||||
import Utils from '../Utils';
|
||||
import * as SchemaController from './SchemaController';
|
||||
import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
|
||||
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||
import SchemaCache from '../Adapters/Cache/SchemaCache';
|
||||
import type { LoadSchemaOptions } from './types';
|
||||
import type { ParseServerOptions } from '../Options';
|
||||
import type { QueryOptions, FullQueryOptions } from '../Adapters/Storage/StorageAdapter';
|
||||
|
||||
function addWriteACL(query, acl) {
|
||||
@@ -125,7 +127,7 @@ const filterSensitiveData = (
|
||||
aclGroup: any[],
|
||||
auth: any,
|
||||
operation: any,
|
||||
schema: SchemaController.SchemaController,
|
||||
schema: SchemaController.SchemaController | any,
|
||||
className: string,
|
||||
protectedFields: null | Array<any>,
|
||||
object: any
|
||||
@@ -134,7 +136,8 @@ const filterSensitiveData = (
|
||||
if (auth && auth.user) userId = auth.user.id;
|
||||
|
||||
// replace protectedFields when using pointer-permissions
|
||||
const perms = schema.getClassLevelPermissions(className);
|
||||
const perms =
|
||||
schema && schema.getClassLevelPermissions ? schema.getClassLevelPermissions(className) : {};
|
||||
if (perms) {
|
||||
const isReadOperation = ['get', 'find'].indexOf(operation) > -1;
|
||||
|
||||
@@ -258,41 +261,6 @@ const isSpecialUpdateKey = key => {
|
||||
return specialKeysForUpdate.indexOf(key) >= 0;
|
||||
};
|
||||
|
||||
function expandResultOnKeyPath(object, key, value) {
|
||||
if (key.indexOf('.') < 0) {
|
||||
object[key] = value[key];
|
||||
return object;
|
||||
}
|
||||
const path = key.split('.');
|
||||
const firstKey = path[0];
|
||||
const nextPath = path.slice(1).join('.');
|
||||
object[firstKey] = expandResultOnKeyPath(object[firstKey] || {}, nextPath, value[firstKey]);
|
||||
delete object[key];
|
||||
return object;
|
||||
}
|
||||
|
||||
function sanitizeDatabaseResult(originalObject, result): Promise<any> {
|
||||
const response = {};
|
||||
if (!result) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
Object.keys(originalObject).forEach(key => {
|
||||
const keyUpdate = originalObject[key];
|
||||
// determine if that was an op
|
||||
if (
|
||||
keyUpdate &&
|
||||
typeof keyUpdate === 'object' &&
|
||||
keyUpdate.__op &&
|
||||
['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1
|
||||
) {
|
||||
// only valid ops that produce an actionable result
|
||||
// the op may have happend on a keypath
|
||||
expandResultOnKeyPath(response, key, result);
|
||||
}
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
function joinTableName(className, key) {
|
||||
return `_Join:${key}:${className}`;
|
||||
}
|
||||
@@ -395,19 +363,21 @@ const relationSchema = {
|
||||
|
||||
class DatabaseController {
|
||||
adapter: StorageAdapter;
|
||||
idempotencyOptions: any;
|
||||
schemaCache: any;
|
||||
schemaPromise: ?Promise<SchemaController.SchemaController>;
|
||||
_transactionalSession: ?any;
|
||||
options: ParseServerOptions;
|
||||
idempotencyOptions: any;
|
||||
|
||||
constructor(adapter: StorageAdapter, idempotencyOptions?: Object = {}) {
|
||||
constructor(adapter: StorageAdapter, options: ParseServerOptions) {
|
||||
this.adapter = adapter;
|
||||
this.idempotencyOptions = idempotencyOptions;
|
||||
// We don't want a mutable this.schema, because then you could have
|
||||
// one request that uses different schemas for different parts of
|
||||
// it. Instead, use loadSchema to get a schema.
|
||||
this.options = options || {};
|
||||
this.idempotencyOptions = this.options.idempotencyOptions || {};
|
||||
// Prevent mutable this.schema, otherwise one request could use
|
||||
// multiple schemas, so instead use loadSchema to get a schema.
|
||||
this.schemaPromise = null;
|
||||
this._transactionalSession = null;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
collectionExists(className: string): Promise<boolean> {
|
||||
@@ -646,7 +616,7 @@ class DatabaseController {
|
||||
if (skipSanitization) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
return sanitizeDatabaseResult(originalUpdate, result);
|
||||
return this._sanitizeDatabaseResult(originalUpdate, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -873,7 +843,7 @@ class DatabaseController {
|
||||
object,
|
||||
relationUpdates
|
||||
).then(() => {
|
||||
return sanitizeDatabaseResult(originalObject, result.ops[0]);
|
||||
return this._sanitizeDatabaseResult(originalObject, result.ops[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1564,14 +1534,17 @@ class DatabaseController {
|
||||
}
|
||||
|
||||
addProtectedFields(
|
||||
schema: SchemaController.SchemaController,
|
||||
schema: SchemaController.SchemaController | any,
|
||||
className: string,
|
||||
query: any = {},
|
||||
aclGroup: any[] = [],
|
||||
auth: any = {},
|
||||
queryOptions: FullQueryOptions = {}
|
||||
): null | string[] {
|
||||
const perms = schema.getClassLevelPermissions(className);
|
||||
const perms =
|
||||
schema && schema.getClassLevelPermissions
|
||||
? schema.getClassLevelPermissions(className)
|
||||
: schema;
|
||||
if (!perms) return null;
|
||||
|
||||
const protectedFields = perms.protectedFields;
|
||||
@@ -1782,9 +1755,65 @@ class DatabaseController {
|
||||
await this.adapter.updateSchemaWithIndexes();
|
||||
}
|
||||
|
||||
_expandResultOnKeyPath(object: any, key: string, value: any): any {
|
||||
if (key.indexOf('.') < 0) {
|
||||
object[key] = value[key];
|
||||
return object;
|
||||
}
|
||||
const path = key.split('.');
|
||||
const firstKey = path[0];
|
||||
const nextPath = path.slice(1).join('.');
|
||||
|
||||
// Scan request data for denied keywords
|
||||
if (this.options && this.options.requestKeywordDenylist) {
|
||||
// Scan request data for denied keywords
|
||||
for (const keyword of this.options.requestKeywordDenylist) {
|
||||
const match = Utils.objectContainsKeyValue({ firstKey: undefined }, keyword.key, undefined);
|
||||
if (match) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
`Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object[firstKey] = this._expandResultOnKeyPath(
|
||||
object[firstKey] || {},
|
||||
nextPath,
|
||||
value[firstKey]
|
||||
);
|
||||
delete object[key];
|
||||
return object;
|
||||
}
|
||||
|
||||
_sanitizeDatabaseResult(originalObject: any, result: any): Promise<any> {
|
||||
const response = {};
|
||||
if (!result) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
Object.keys(originalObject).forEach(key => {
|
||||
const keyUpdate = originalObject[key];
|
||||
// determine if that was an op
|
||||
if (
|
||||
keyUpdate &&
|
||||
typeof keyUpdate === 'object' &&
|
||||
keyUpdate.__op &&
|
||||
['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1
|
||||
) {
|
||||
// only valid ops that produce an actionable result
|
||||
// the op may have happened on a keypath
|
||||
this._expandResultOnKeyPath(response, key, result);
|
||||
}
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
static _validateQuery: any => void;
|
||||
static filterSensitiveData: (boolean, any[], any, any, any, string, any[], any) => void;
|
||||
}
|
||||
|
||||
module.exports = DatabaseController;
|
||||
// Expose validateQuery for tests
|
||||
module.exports._validateQuery = validateQuery;
|
||||
module.exports.filterSensitiveData = filterSensitiveData;
|
||||
|
||||
@@ -142,7 +142,7 @@ export function getLiveQueryController(options: ParseServerOptions): LiveQueryCo
|
||||
}
|
||||
|
||||
export function getDatabaseController(options: ParseServerOptions): DatabaseController {
|
||||
const { databaseURI, collectionPrefix, databaseOptions, idempotencyOptions } = options;
|
||||
const { databaseURI, collectionPrefix, databaseOptions } = options;
|
||||
let { databaseAdapter } = options;
|
||||
if (
|
||||
(databaseOptions ||
|
||||
@@ -156,7 +156,7 @@ export function getDatabaseController(options: ParseServerOptions): DatabaseCont
|
||||
} else {
|
||||
databaseAdapter = loadAdapter(databaseAdapter);
|
||||
}
|
||||
return new DatabaseController(databaseAdapter, idempotencyOptions);
|
||||
return new DatabaseController(databaseAdapter, options);
|
||||
}
|
||||
|
||||
export function getHooksController(
|
||||
|
||||
@@ -33,6 +33,9 @@ class ParseCloudCodePublisher {
|
||||
if (request.original) {
|
||||
message.originalParseObject = request.original._toFullJSON();
|
||||
}
|
||||
if (request.classLevelPermissions) {
|
||||
message.classLevelPermissions = request.classLevelPermissions;
|
||||
}
|
||||
this.parsePublisher.publish(type, JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,18 @@ import { ParsePubSub } from './ParsePubSub';
|
||||
import SchemaController from '../Controllers/SchemaController';
|
||||
import _ from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
|
||||
import {
|
||||
runLiveQueryEventHandlers,
|
||||
getTrigger,
|
||||
runTrigger,
|
||||
resolveError,
|
||||
toJSONwithObjects,
|
||||
} from '../triggers';
|
||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||
import { getCacheController } from '../Controllers';
|
||||
import { getCacheController, getDatabaseController } from '../Controllers';
|
||||
import LRU from 'lru-cache';
|
||||
import UserRouter from '../Routers/UsersRouter';
|
||||
import DatabaseController from '../Controllers/DatabaseController';
|
||||
|
||||
class ParseLiveQueryServer {
|
||||
clients: Map;
|
||||
@@ -185,23 +192,18 @@ class ParseLiveQueryServer {
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
|
||||
}
|
||||
if (
|
||||
(deletedParseObject.className === '_User' ||
|
||||
deletedParseObject.className === '_Session') &&
|
||||
!client.hasMasterKey
|
||||
) {
|
||||
delete deletedParseObject.sessionToken;
|
||||
delete deletedParseObject.authData;
|
||||
}
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || Parse.Error.SCRIPT_FAILED,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
await this._filterSensitiveData(
|
||||
classLevelPermissions,
|
||||
res,
|
||||
client,
|
||||
requestId,
|
||||
op,
|
||||
subscription.query
|
||||
);
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
} catch (e) {
|
||||
const error = resolveError(e);
|
||||
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
@@ -344,28 +346,21 @@ class ParseLiveQueryServer {
|
||||
res.original.className || className
|
||||
);
|
||||
}
|
||||
if (
|
||||
(currentParseObject.className === '_User' ||
|
||||
currentParseObject.className === '_Session') &&
|
||||
!client.hasMasterKey
|
||||
) {
|
||||
delete currentParseObject.sessionToken;
|
||||
delete originalParseObject?.sessionToken;
|
||||
delete currentParseObject.authData;
|
||||
delete originalParseObject?.authData;
|
||||
}
|
||||
await this._filterSensitiveData(
|
||||
classLevelPermissions,
|
||||
res,
|
||||
client,
|
||||
requestId,
|
||||
op,
|
||||
subscription.query
|
||||
);
|
||||
const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1);
|
||||
if (client[functionName]) {
|
||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||
}
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
client.parseWebSocket,
|
||||
error.code || Parse.Error.SCRIPT_FAILED,
|
||||
error.message || error,
|
||||
false,
|
||||
requestId
|
||||
);
|
||||
} catch (e) {
|
||||
const error = resolveError(e);
|
||||
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
|
||||
logger.error(
|
||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
@@ -550,6 +545,54 @@ class ParseLiveQueryServer {
|
||||
// return rolesQuery.find({useMasterKey:true});
|
||||
}
|
||||
|
||||
async _filterSensitiveData(
|
||||
classLevelPermissions: ?any,
|
||||
res: any,
|
||||
client: any,
|
||||
requestId: number,
|
||||
op: string,
|
||||
query: any
|
||||
) {
|
||||
const subscriptionInfo = client.getSubscriptionInfo(requestId);
|
||||
const aclGroup = ['*'];
|
||||
let clientAuth;
|
||||
if (typeof subscriptionInfo !== 'undefined') {
|
||||
const { userId, auth } = await this.getAuthForSessionToken(subscriptionInfo.sessionToken);
|
||||
if (userId) {
|
||||
aclGroup.push(userId);
|
||||
}
|
||||
clientAuth = auth;
|
||||
}
|
||||
const filter = obj => {
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
let protectedFields = classLevelPermissions?.protectedFields || [];
|
||||
if (!client.hasMasterKey && !Array.isArray(protectedFields)) {
|
||||
protectedFields = getDatabaseController(this.config).addProtectedFields(
|
||||
classLevelPermissions,
|
||||
res.object.className,
|
||||
query,
|
||||
aclGroup,
|
||||
clientAuth
|
||||
);
|
||||
}
|
||||
return DatabaseController.filterSensitiveData(
|
||||
client.hasMasterKey,
|
||||
aclGroup,
|
||||
clientAuth,
|
||||
op,
|
||||
classLevelPermissions,
|
||||
res.object.className,
|
||||
protectedFields,
|
||||
obj,
|
||||
query
|
||||
);
|
||||
};
|
||||
res.object = filter(res.object);
|
||||
res.original = filter(res.original);
|
||||
}
|
||||
|
||||
_getCLPOperation(query: any) {
|
||||
return typeof query === 'object' &&
|
||||
Object.keys(query).length == 1 &&
|
||||
@@ -681,13 +724,9 @@ class ParseLiveQueryServer {
|
||||
logger.info(`Create new client: ${parseWebsocket.clientId}`);
|
||||
client.pushConnect();
|
||||
runLiveQueryEventHandlers(req);
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
error.code || Parse.Error.SCRIPT_FAILED,
|
||||
error.message || error,
|
||||
false
|
||||
);
|
||||
} catch (e) {
|
||||
const error = resolveError(e);
|
||||
Client.pushError(parseWebsocket, error.code, error.message, false);
|
||||
logger.error(
|
||||
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
@@ -827,16 +866,11 @@ class ParseLiveQueryServer {
|
||||
installationId: client.installationId,
|
||||
});
|
||||
} catch (e) {
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
e.code || Parse.Error.SCRIPT_FAILED,
|
||||
e.message || e,
|
||||
false,
|
||||
request.requestId
|
||||
);
|
||||
const error = resolveError(e);
|
||||
Client.pushError(parseWebsocket, error.code, error.message, false, request.requestId);
|
||||
logger.error(
|
||||
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(e)
|
||||
JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +350,24 @@ module.exports.ParseServerOptions = {
|
||||
env: 'PARSE_SERVER_READ_ONLY_MASTER_KEY',
|
||||
help: 'Read-only key, which has the same capabilities as MasterKey without writes',
|
||||
},
|
||||
requestKeywordDenylist: {
|
||||
env: 'PARSE_SERVER_REQUEST_KEYWORD_DENYLIST',
|
||||
help:
|
||||
'An array of keys and values that are prohibited in database read and write requests to prevent potential security vulnerabilities. It is possible to specify only a key (`{"key":"..."}`), only a value (`{"value":"..."}`) or a key-value pair (`{"key":"...","value":"..."}`). The specification can use the following types: `boolean`, `numeric` or `string`, where `string` will be interpreted as a regex notation. Request data is deep-scanned for matching definitions to detect also any nested occurrences. Defaults are patterns that are likely to be used in malicious requests. Setting this option will override the default patterns.',
|
||||
action: parsers.arrayParser,
|
||||
default: [
|
||||
{
|
||||
key: '_bsontype',
|
||||
value: 'Code',
|
||||
},
|
||||
{
|
||||
key: 'constructor',
|
||||
},
|
||||
{
|
||||
key: '__proto__',
|
||||
},
|
||||
],
|
||||
},
|
||||
restAPIKey: {
|
||||
env: 'PARSE_SERVER_REST_API_KEY',
|
||||
help: 'Key for REST calls',
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
* @property {String} publicServerURL Public URL to your parse server with http:// or https://.
|
||||
* @property {Any} push Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications
|
||||
* @property {String} readOnlyMasterKey Read-only key, which has the same capabilities as MasterKey without writes
|
||||
* @property {RequestKeywordDenylist[]} requestKeywordDenylist An array of keys and values that are prohibited in database read and write requests to prevent potential security vulnerabilities. It is possible to specify only a key (`{"key":"..."}`), only a value (`{"value":"..."}`) or a key-value pair (`{"key":"...","value":"..."}`). The specification can use the following types: `boolean`, `numeric` or `string`, where `string` will be interpreted as a regex notation. Request data is deep-scanned for matching definitions to detect also any nested occurrences. Defaults are patterns that are likely to be used in malicious requests. Setting this option will override the default patterns.
|
||||
* @property {String} restAPIKey Key for REST calls
|
||||
* @property {Boolean} revokeSessionOnPasswordReset When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
|
||||
* @property {Boolean} scheduledPush Configuration for push scheduling, defaults to false.
|
||||
|
||||
@@ -14,6 +14,10 @@ type Adapter<T> = string | any | T;
|
||||
type NumberOrBoolean = number | boolean;
|
||||
type NumberOrString = number | string;
|
||||
type ProtectedFields = any;
|
||||
type RequestKeywordDenylist = {
|
||||
key: string | any,
|
||||
value: any,
|
||||
};
|
||||
|
||||
export interface ParseServerOptions {
|
||||
/* Your Parse Application ID
|
||||
@@ -252,6 +256,9 @@ export interface ParseServerOptions {
|
||||
/* Set to true if new users should be created without public read and write access.
|
||||
:DEFAULT: false */
|
||||
enforcePrivateUsers: ?boolean;
|
||||
/* An array of keys and values that are prohibited in database read and write requests to prevent potential security vulnerabilities. It is possible to specify only a key (`{"key":"..."}`), only a value (`{"value":"..."}`) or a key-value pair (`{"key":"...","value":"..."}`). The specification can use the following types: `boolean`, `numeric` or `string`, where `string` will be interpreted as a regex notation. Request data is deep-scanned for matching definitions to detect also any nested occurrences. Defaults are patterns that are likely to be used in malicious requests. Setting this option will override the default patterns.
|
||||
:DEFAULT: [{"key":"_bsontype","value":"Code"},{"key":"constructor"},{"key":"__proto__"}] */
|
||||
requestKeywordDenylist: ?(RequestKeywordDenylist[]);
|
||||
}
|
||||
|
||||
export interface SecurityOptions {
|
||||
|
||||
@@ -6,6 +6,7 @@ var SchemaController = require('./Controllers/SchemaController');
|
||||
var deepcopy = require('deepcopy');
|
||||
|
||||
const Auth = require('./Auth');
|
||||
const Utils = require('./Utils');
|
||||
var cryptoUtils = require('./cryptoUtils');
|
||||
var passwordCrypto = require('./password');
|
||||
var Parse = require('parse/node');
|
||||
@@ -61,6 +62,19 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK
|
||||
}
|
||||
}
|
||||
|
||||
if (this.config.requestKeywordDenylist) {
|
||||
// Scan request data for denied keywords
|
||||
for (const keyword of this.config.requestKeywordDenylist) {
|
||||
const match = Utils.objectContainsKeyValue(data, keyword.key, keyword.value);
|
||||
if (match) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
`Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the operation is complete, this.response may have several
|
||||
// fields.
|
||||
// response: the actual data to be returned
|
||||
|
||||
@@ -66,6 +66,12 @@ export class FilesRouter {
|
||||
|
||||
getHandler(req, res) {
|
||||
const config = Config.get(req.params.appId);
|
||||
if (!config) {
|
||||
res.status(403);
|
||||
const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
|
||||
res.json({ code: err.code, error: err.message });
|
||||
return;
|
||||
}
|
||||
const filesController = config.filesController;
|
||||
const filename = req.params.filename;
|
||||
const contentType = mime.getType(filename);
|
||||
@@ -250,10 +256,10 @@ export class FilesRouter {
|
||||
}
|
||||
|
||||
async metadataHandler(req, res) {
|
||||
const config = Config.get(req.params.appId);
|
||||
const { filesController } = config;
|
||||
const { filename } = req.params;
|
||||
try {
|
||||
const config = Config.get(req.params.appId);
|
||||
const { filesController } = config;
|
||||
const { filename } = req.params;
|
||||
const data = await filesController.getMetadata(filename);
|
||||
res.status(200);
|
||||
res.json(data);
|
||||
|
||||
@@ -332,6 +332,32 @@ class Utils {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep-scans an object for a matching key/value definition.
|
||||
* @param {Object} obj The object to scan.
|
||||
* @param {String | undefined} key The key to match, or undefined if only the value should be matched.
|
||||
* @param {any | undefined} value The value to match, or undefined if only the key should be matched.
|
||||
* @returns {Boolean} True if a match was found, false otherwise.
|
||||
*/
|
||||
static objectContainsKeyValue(obj, key, value) {
|
||||
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b;
|
||||
const isKeyMatch = k => isMatch(k, key);
|
||||
const isValueMatch = v => isMatch(v, value);
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (key !== undefined && value === undefined && isKeyMatch(k)) {
|
||||
return true;
|
||||
} else if (key === undefined && value !== undefined && isValueMatch(v)) {
|
||||
return true;
|
||||
} else if (key !== undefined && value !== undefined && isKeyMatch(k) && isValueMatch(v)) {
|
||||
return true;
|
||||
}
|
||||
if (['[object Object]', '[object Array]'].includes(Object.prototype.toString.call(v))) {
|
||||
return Utils.objectContainsKeyValue(v, key, value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utils;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ function mountOnto(router) {
|
||||
function parseURL(urlString) {
|
||||
try {
|
||||
return new URL(urlString);
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -432,7 +432,12 @@ export function promiseEnforceMasterKeyAccess(request) {
|
||||
*/
|
||||
export function promiseEnsureIdempotency(req) {
|
||||
// Enable feature only for MongoDB
|
||||
if (!((req.config.database.adapter instanceof MongoStorageAdapter) || (req.config.database.adapter instanceof PostgresStorageAdapter))) {
|
||||
if (
|
||||
!(
|
||||
req.config.database.adapter instanceof MongoStorageAdapter ||
|
||||
req.config.database.adapter instanceof PostgresStorageAdapter
|
||||
)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Get parameters
|
||||
|
||||
Reference in New Issue
Block a user