mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
81
Commits
6.4.0-alpha.3
...
6.5.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
586fdad67b | ||
|
|
dfbafa7025 | ||
|
|
1c2b448284 | ||
|
|
97627ee450 | ||
|
|
144781a855 | ||
|
|
1bfbccf9ee | ||
|
|
12ce46db81 | ||
|
|
d5290d46e5 | ||
|
|
52729fd15c | ||
|
|
f332d54577 | ||
|
|
3012ff72bb | ||
|
|
0d5e01c26b | ||
|
|
09ead54626 | ||
|
|
0e92f765e4 | ||
|
|
acea93c8a6 | ||
|
|
490898e11d | ||
|
|
63db281976 | ||
|
|
72ba762390 | ||
|
|
eba0da47ba | ||
|
|
dc53521243 | ||
|
|
9dc0235b5d | ||
|
|
5ae6d6a36d | ||
|
|
3773203a37 | ||
|
|
8ff444d42e | ||
|
|
9cb44c08cc | ||
|
|
09b6a95264 | ||
|
|
422958e246 | ||
|
|
b8535b3db9 | ||
|
|
9282bc595c | ||
|
|
47184f0734 | ||
|
|
d53c1f3668 | ||
|
|
d3ec2e2be7 | ||
|
|
0fa0aabefe | ||
|
|
46761d3ae2 | ||
|
|
bba24dd827 | ||
|
|
30258be121 | ||
|
|
5f9a27fb8e | ||
|
|
297faaece4 | ||
|
|
a6e6549435 | ||
|
|
244e3431cf | ||
|
|
33c648dc81 | ||
|
|
4524c35d51 | ||
|
|
70e0cb3744 | ||
|
|
519dee9b89 | ||
|
|
897acb76a5 | ||
|
|
e5de9daa18 | ||
|
|
223fde0f31 | ||
|
|
d0a5af33ca | ||
|
|
8fe0ae7a2c | ||
|
|
5179501885 | ||
|
|
933e8226df | ||
|
|
d3087ed69f | ||
|
|
b87daba067 | ||
|
|
759731926f | ||
|
|
0762ba6b79 | ||
|
|
6ef1986c03 | ||
|
|
988ecdac54 | ||
|
|
87059ae1d6 | ||
|
|
42e2e12956 | ||
|
|
d58032d39d | ||
|
|
5b1bb598e4 | ||
|
|
2612a387ce | ||
|
|
c6355cda73 | ||
|
|
90aac622b0 | ||
|
|
4b3ce20300 | ||
|
|
09fbeebba8 | ||
|
|
80b987d00d | ||
|
|
9e0094980f | ||
|
|
f630a45aa5 | ||
|
|
ea57a7706d | ||
|
|
fe02d3e8aa | ||
|
|
b0c012e835 | ||
|
|
fd86278919 | ||
|
|
5dd3aa0d48 | ||
|
|
2d6b3d1849 | ||
|
|
93af48a8b4 | ||
|
|
5462834240 | ||
|
|
7d32d8934f | ||
|
|
8d3117e0bc | ||
|
|
a2a98b1684 | ||
|
|
77bbfb3f18 |
@@ -123,14 +123,14 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64, linux/arm64/v8
|
||||
check-lock-file-version:
|
||||
name: NPM Lock File Version
|
||||
timeout-minutes: 5
|
||||
|
||||
@@ -56,18 +56,18 @@ jobs:
|
||||
ref: ${{ needs.release.outputs.current_tag }}
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Log into Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
@@ -75,10 +75,10 @@ jobs:
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=${{ needs.release.outputs.current_tag }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8
|
||||
platforms: linux/amd64, linux/arm64/v8
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -112,4 +112,4 @@ jobs:
|
||||
uses: peaceiris/actions-gh-pages@v3.7.3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
publish_dir: ./docs
|
||||
|
||||
@@ -28,18 +28,18 @@ jobs:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Log into Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
@@ -48,10 +48,10 @@ jobs:
|
||||
type=semver,enable=true,pattern={{version}},value=${{ github.event.inputs.ref }}
|
||||
type=raw,enable=${{ github.event.inputs.ref == '' }},value=latest
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8
|
||||
platforms: linux/amd64, linux/arm64/v8
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
############################################################
|
||||
# Build stage
|
||||
############################################################
|
||||
FROM node:lts-alpine AS build
|
||||
FROM node:18-alpine AS build
|
||||
|
||||
RUN apk --no-cache add git
|
||||
WORKDIR /tmp
|
||||
@@ -24,7 +24,7 @@ RUN npm ci --omit=dev --ignore-scripts \
|
||||
############################################################
|
||||
# Release stage
|
||||
############################################################
|
||||
FROM node:lts-alpine AS release
|
||||
FROM node:18-alpine AS release
|
||||
|
||||
VOLUME /parse-server/cloud /parse-server/config
|
||||
|
||||
|
||||
@@ -358,12 +358,14 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
|
||||
## Access Scopes
|
||||
|
||||
| Scope | Internal data | Custom data | Restricted by CLP, ACL | Key |
|
||||
|----------------|---------------|-------------|------------------------|---------------------|
|
||||
| Internal | r/w | r/w | no | `maintenanceKey` |
|
||||
| Master | -/- | r/w | no | `masterKey` |
|
||||
| ReadOnlyMaster | -/- | r/- | no | `readOnlyMasterKey` |
|
||||
| Session | -/- | r/w | yes | `sessionToken` |
|
||||
| Scope | Internal data | Read-only data <sub>(1)</sub> | Custom data | Restricted by CLP, ACL | Key |
|
||||
|----------------|---------------|-------------------------------|-------------|------------------------|---------------------|
|
||||
| Internal | r/w | r/w | r/w | no | `maintenanceKey` |
|
||||
| Master | -/- | r/- | r/w | no | `masterKey` |
|
||||
| ReadOnlyMaster | -/- | r/- | r/- | no | `readOnlyMasterKey` |
|
||||
| Session | -/- | r/- | r/w | yes | `sessionToken` |
|
||||
|
||||
<sub>(1) `Parse.Object.createdAt`, `Parse.Object.updatedAt`.</sub>
|
||||
|
||||
## Email Verification and Password Reset
|
||||
|
||||
|
||||
@@ -1,3 +1,63 @@
|
||||
# [6.5.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.5.0-alpha.1...6.5.0-alpha.2) (2023-11-19)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Improved IP validation performance for `masterKeyIPs`, `maintenanceKeyIPs` ([#8510](https://github.com/parse-community/parse-server/issues/8510)) ([b87daba](https://github.com/parse-community/parse-server/commit/b87daba0671a1b0b7b8d63bc671d665c91a04522))
|
||||
|
||||
# [6.5.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.4.0...6.5.0-alpha.1) (2023-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Context not passed to Cloud Code Trigger `beforeFind` when using `Parse.Query.include` ([#8765](https://github.com/parse-community/parse-server/issues/8765)) ([7d32d89](https://github.com/parse-community/parse-server/commit/7d32d8934f3ae7af7a7d8b9cc6a829c7d73973d3))
|
||||
* Parse Server option `fileUpload.fileExtensions` fails to determine file extension if filename contains multiple dots ([#8754](https://github.com/parse-community/parse-server/issues/8754)) ([3d6d50e](https://github.com/parse-community/parse-server/commit/3d6d50e0afff18b95fb906914e2cebd3839b517a))
|
||||
* Security bump @babel/traverse from 7.20.5 to 7.23.2 ([#8777](https://github.com/parse-community/parse-server/issues/8777)) ([2d6b3d1](https://github.com/parse-community/parse-server/commit/2d6b3d18499179e99be116f25c0850d3f449509c))
|
||||
* Security upgrade graphql from 16.6.0 to 16.8.1 ([#8758](https://github.com/parse-community/parse-server/issues/8758)) ([71dfd8a](https://github.com/parse-community/parse-server/commit/71dfd8a7ece8c0dd1a66d03bb9420cfd39f4f9b1))
|
||||
|
||||
### Features
|
||||
|
||||
* Add `$setOnInsert` operator to `Parse.Server.database.update` ([#8791](https://github.com/parse-community/parse-server/issues/8791)) ([f630a45](https://github.com/parse-community/parse-server/commit/f630a45aa5e87bc73a81fded061400c199b71a29))
|
||||
* Add compatibility for MongoDB Atlas Serverless and AWS Amazon DocumentDB with collation options `enableCollationCaseComparison`, `transformEmailToLowercase`, `transformUsernameToLowercase` ([#8805](https://github.com/parse-community/parse-server/issues/8805)) ([09fbeeb](https://github.com/parse-community/parse-server/commit/09fbeebba8870e7cf371fb84371a254c7b368620))
|
||||
* Add context to Cloud Code Triggers `beforeLogin` and `afterLogin` ([#8724](https://github.com/parse-community/parse-server/issues/8724)) ([a9c34ef](https://github.com/parse-community/parse-server/commit/a9c34ef1e2c78a42fb8b5fa8d569b7677c74919d))
|
||||
* Allow setting `createdAt` and `updatedAt` during `Parse.Object` creation with maintenance key ([#8696](https://github.com/parse-community/parse-server/issues/8696)) ([77bbfb3](https://github.com/parse-community/parse-server/commit/77bbfb3f186f5651c33ba152f04cff95128eaf2d))
|
||||
* Upgrade Parse Server Push Adapter to 5.0.2 ([#8813](https://github.com/parse-community/parse-server/issues/8813)) ([6ef1986](https://github.com/parse-community/parse-server/commit/6ef1986c03a1d84b7e11c05851e5bf9688d88740))
|
||||
|
||||
# [6.4.0-alpha.8](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.7...6.4.0-alpha.8) (2023-11-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add compatibility for MongoDB Atlas Serverless and AWS Amazon DocumentDB with collation options `enableCollationCaseComparison`, `transformEmailToLowercase`, `transformUsernameToLowercase` ([#8805](https://github.com/parse-community/parse-server/issues/8805)) ([09fbeeb](https://github.com/parse-community/parse-server/commit/09fbeebba8870e7cf371fb84371a254c7b368620))
|
||||
|
||||
# [6.4.0-alpha.7](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.6...6.4.0-alpha.7) (2023-10-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `$setOnInsert` operator to `Parse.Server.database.update` ([#8791](https://github.com/parse-community/parse-server/issues/8791)) ([f630a45](https://github.com/parse-community/parse-server/commit/f630a45aa5e87bc73a81fded061400c199b71a29))
|
||||
|
||||
# [6.4.0-alpha.6](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.5...6.4.0-alpha.6) (2023-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security bump @babel/traverse from 7.20.5 to 7.23.2 ([#8777](https://github.com/parse-community/parse-server/issues/8777)) ([2d6b3d1](https://github.com/parse-community/parse-server/commit/2d6b3d18499179e99be116f25c0850d3f449509c))
|
||||
|
||||
# [6.4.0-alpha.5](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.4...6.4.0-alpha.5) (2023-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Context not passed to Cloud Code Trigger `beforeFind` when using `Parse.Query.include` ([#8765](https://github.com/parse-community/parse-server/issues/8765)) ([7d32d89](https://github.com/parse-community/parse-server/commit/7d32d8934f3ae7af7a7d8b9cc6a829c7d73973d3))
|
||||
|
||||
# [6.4.0-alpha.4](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.3...6.4.0-alpha.4) (2023-09-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow setting `createdAt` and `updatedAt` during `Parse.Object` creation with maintenance key ([#8696](https://github.com/parse-community/parse-server/issues/8696)) ([77bbfb3](https://github.com/parse-community/parse-server/commit/77bbfb3f186f5651c33ba152f04cff95128eaf2d))
|
||||
|
||||
# [6.4.0-alpha.3](https://github.com/parse-community/parse-server/compare/6.4.0-alpha.2...6.4.0-alpha.3) (2023-09-23)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
# [6.5.0-beta.1](https://github.com/parse-community/parse-server/compare/6.4.0...6.5.0-beta.1) (2023-11-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Context not passed to Cloud Code Trigger `beforeFind` when using `Parse.Query.include` ([#8765](https://github.com/parse-community/parse-server/issues/8765)) ([7d32d89](https://github.com/parse-community/parse-server/commit/7d32d8934f3ae7af7a7d8b9cc6a829c7d73973d3))
|
||||
* Parse Server option `fileUpload.fileExtensions` fails to determine file extension if filename contains multiple dots ([#8754](https://github.com/parse-community/parse-server/issues/8754)) ([3d6d50e](https://github.com/parse-community/parse-server/commit/3d6d50e0afff18b95fb906914e2cebd3839b517a))
|
||||
* Security bump @babel/traverse from 7.20.5 to 7.23.2 ([#8777](https://github.com/parse-community/parse-server/issues/8777)) ([2d6b3d1](https://github.com/parse-community/parse-server/commit/2d6b3d18499179e99be116f25c0850d3f449509c))
|
||||
* Security upgrade graphql from 16.6.0 to 16.8.1 ([#8758](https://github.com/parse-community/parse-server/issues/8758)) ([71dfd8a](https://github.com/parse-community/parse-server/commit/71dfd8a7ece8c0dd1a66d03bb9420cfd39f4f9b1))
|
||||
|
||||
### Features
|
||||
|
||||
* Add `$setOnInsert` operator to `Parse.Server.database.update` ([#8791](https://github.com/parse-community/parse-server/issues/8791)) ([f630a45](https://github.com/parse-community/parse-server/commit/f630a45aa5e87bc73a81fded061400c199b71a29))
|
||||
* Add compatibility for MongoDB Atlas Serverless and AWS Amazon DocumentDB with collation options `enableCollationCaseComparison`, `transformEmailToLowercase`, `transformUsernameToLowercase` ([#8805](https://github.com/parse-community/parse-server/issues/8805)) ([09fbeeb](https://github.com/parse-community/parse-server/commit/09fbeebba8870e7cf371fb84371a254c7b368620))
|
||||
* Add context to Cloud Code Triggers `beforeLogin` and `afterLogin` ([#8724](https://github.com/parse-community/parse-server/issues/8724)) ([a9c34ef](https://github.com/parse-community/parse-server/commit/a9c34ef1e2c78a42fb8b5fa8d569b7677c74919d))
|
||||
* Allow setting `createdAt` and `updatedAt` during `Parse.Object` creation with maintenance key ([#8696](https://github.com/parse-community/parse-server/issues/8696)) ([77bbfb3](https://github.com/parse-community/parse-server/commit/77bbfb3f186f5651c33ba152f04cff95128eaf2d))
|
||||
|
||||
# [6.4.0-beta.1](https://github.com/parse-community/parse-server/compare/6.3.0...6.4.0-beta.1) (2023-09-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,114 @@
|
||||
## [6.5.11](https://github.com/parse-community/parse-server/compare/6.5.10...6.5.11) (2024-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade to path-to-regexp 6.2.2 ([#9365](https://github.com/parse-community/parse-server/issues/9365)) ([dfbafa7](https://github.com/parse-community/parse-server/commit/dfbafa702507da0d20667eabe8fdc835ca30c8e8))
|
||||
|
||||
## [6.5.10](https://github.com/parse-community/parse-server/compare/6.5.9...6.5.10) (2024-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade express from 4.21.0 to 4.21.1 ([#9339](https://github.com/parse-community/parse-server/issues/9339)) ([97627ee](https://github.com/parse-community/parse-server/commit/97627ee450f5abe04c92609f4ae2e63c49060d6a))
|
||||
|
||||
## [6.5.9](https://github.com/parse-community/parse-server/compare/6.5.8...6.5.9) (2024-10-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Custom object ID allows to acquire role privileges ([GHSA-8xq9-g7ch-35hg](https://github.com/parse-community/parse-server/security/advisories/GHSA-8xq9-g7ch-35hg)) ([#9318](https://github.com/parse-community/parse-server/issues/9318)) ([1bfbccf](https://github.com/parse-community/parse-server/commit/1bfbccf9ee7ea77533b2b2aa7c4c69f3bd35e66f))
|
||||
|
||||
## [6.5.8](https://github.com/parse-community/parse-server/compare/6.5.7...6.5.8) (2024-09-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Various vulnerabilities related to cross-site scripting ([#9310](https://github.com/parse-community/parse-server/issues/9310)) ([d5290d4](https://github.com/parse-community/parse-server/commit/d5290d46e5ff9237970ae1ac2d2df4051cbf53e5))
|
||||
|
||||
## [6.5.7](https://github.com/parse-community/parse-server/compare/6.5.6...6.5.7) (2024-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* SQL injection when using Parse Server with PostgreSQL; fixes security vulnerability [GHSA-c2hr-cqg6-8j6r](https://github.com/parse-community/parse-server/security/advisories/GHSA-c2hr-cqg6-8j6r) ([#9168](https://github.com/parse-community/parse-server/issues/9168)) ([f332d54](https://github.com/parse-community/parse-server/commit/f332d54577608c5ad927255e06d8c694e2e0ff5b))
|
||||
|
||||
## [6.5.6](https://github.com/parse-community/parse-server/compare/6.5.5...6.5.6) (2024-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Facebook Limited Login not workind due to incorrect domain in JWT validation ([#9120](https://github.com/parse-community/parse-server/issues/9120)) ([0e92f76](https://github.com/parse-community/parse-server/commit/0e92f765e43d1e39285c5958d60cfd7fb76a3c90))
|
||||
|
||||
## [6.5.5](https://github.com/parse-community/parse-server/compare/6.5.4...6.5.5) (2024-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) ([#9023](https://github.com/parse-community/parse-server/issues/9023)) ([5ae6d6a](https://github.com/parse-community/parse-server/commit/5ae6d6a36d75c4511029f0ba5673ae4b2999179b))
|
||||
|
||||
## [6.5.4](https://github.com/parse-community/parse-server/compare/6.5.3...6.5.4) (2024-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Server crashes when receiving an array of `Parse.Pointer` in the request body ([#9012](https://github.com/parse-community/parse-server/issues/9012)) ([8ff444d](https://github.com/parse-community/parse-server/commit/8ff444d42ef51bfe6808c4c3a5fe666bfe302ebe))
|
||||
|
||||
## [6.5.3](https://github.com/parse-community/parse-server/compare/6.5.2...6.5.3) (2024-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade follow-redirects from 1.15.5 to 1.15.6 ([#9019](https://github.com/parse-community/parse-server/issues/9019)) ([422958e](https://github.com/parse-community/parse-server/commit/422958e246da6f13011776c8dde028a00fb821cb))
|
||||
|
||||
## [6.5.2](https://github.com/parse-community/parse-server/compare/6.5.1...6.5.2) (2024-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade @parse/push-adapter from 5.1.0 to 5.1.1 ([#8975](https://github.com/parse-community/parse-server/issues/8975)) ([0fa0aab](https://github.com/parse-community/parse-server/commit/0fa0aabefe6bc9d356ee70be78dafc5fa22d4e17))
|
||||
|
||||
## [6.5.1](https://github.com/parse-community/parse-server/compare/6.5.0...6.5.1) (2024-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Security upgrade @parse/push-adapter from 5.0.2 to 5.1.0 ([#8972](https://github.com/parse-community/parse-server/issues/8972)) ([bba24dd](https://github.com/parse-community/parse-server/commit/bba24dd8279ebb8e4084a5f00fbe3ab9fe6c01b4))
|
||||
|
||||
# [6.5.0](https://github.com/parse-community/parse-server/compare/6.4.0...6.5.0) (2024-03-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Improve PostgreSQL injection detection; fixes security vulnerability [GHSA-6927-3vr9-fxf2](https://github.com/parse-community/parse-server/security/advisories/GHSA-6927-3vr9-fxf2) which affects Parse Server deployments using a Postgres database ([#8960](https://github.com/parse-community/parse-server/issues/8960)) ([a6e6549](https://github.com/parse-community/parse-server/commit/a6e654943536932904a69b51e513507fcf90a504))
|
||||
|
||||
# [6.4.0](https://github.com/parse-community/parse-server/compare/6.3.1...6.4.0) (2023-11-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Parse Server option `fileUpload.fileExtensions` does not work with an array of extensions ([#8688](https://github.com/parse-community/parse-server/issues/8688)) ([6a4a00c](https://github.com/parse-community/parse-server/commit/6a4a00ca7af1163ea74b047b85cd6817366b824b))
|
||||
* Redis 4 does not reconnect after unhandled error ([#8706](https://github.com/parse-community/parse-server/issues/8706)) ([2b3d4e5](https://github.com/parse-community/parse-server/commit/2b3d4e5d3c85cd142f85af68dec51a8523548d49))
|
||||
* Remove config logging when launching Parse Server via CLI ([#8710](https://github.com/parse-community/parse-server/issues/8710)) ([ae68f0c](https://github.com/parse-community/parse-server/commit/ae68f0c31b741eeb83379c905c7ddfaa124436ec))
|
||||
* Server does not start via CLI when `auth` option is set ([#8666](https://github.com/parse-community/parse-server/issues/8666)) ([4e2000b](https://github.com/parse-community/parse-server/commit/4e2000bc563324389584ace3c090a5c1a7796a64))
|
||||
|
||||
### Features
|
||||
|
||||
* Add conditional email verification via dynamic Parse Server options `verifyUserEmails`, `sendUserEmailVerification` that now accept functions ([#8425](https://github.com/parse-community/parse-server/issues/8425)) ([44acd6d](https://github.com/parse-community/parse-server/commit/44acd6d9ed157ad4842200c9d01f9c77a05fec3a))
|
||||
* Add property `Parse.Server.version` to determine current version of Parse Server in Cloud Code ([#8670](https://github.com/parse-community/parse-server/issues/8670)) ([a9d376b](https://github.com/parse-community/parse-server/commit/a9d376b61f5b07806eafbda91c4e36c322f09298))
|
||||
* Add TOTP authentication adapter ([#8457](https://github.com/parse-community/parse-server/issues/8457)) ([cc079a4](https://github.com/parse-community/parse-server/commit/cc079a40f6849a0e9bc6fdc811e8649ecb67b589))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Improve performance of recursive pointer iterations ([#8741](https://github.com/parse-community/parse-server/issues/8741)) ([45a3ed0](https://github.com/parse-community/parse-server/commit/45a3ed0fcf2c0170607505a1550fb15896e705fd))
|
||||
|
||||
## [6.3.1](https://github.com/parse-community/parse-server/compare/6.3.0...6.3.1) (2023-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Server crash when uploading file without extension; fixes security vulnerability [GHSA-792q-q67h-w579](https://github.com/parse-community/parse-server/security/advisories/GHSA-792q-q67h-w579) ([#8781](https://github.com/parse-community/parse-server/issues/8781)) ([fd86278](https://github.com/parse-community/parse-server/commit/fd86278919556d3682e7e2c856dfccd5beffbfc0))
|
||||
|
||||
# [6.3.0](https://github.com/parse-community/parse-server/compare/6.2.2...6.3.0) (2023-09-16)
|
||||
|
||||
|
||||
|
||||
Generated
+2901
-994
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "6.4.0-alpha.3",
|
||||
"version": "6.5.11",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -19,50 +19,49 @@
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "7.21.8",
|
||||
"@babel/eslint-parser": "7.24.1",
|
||||
"@graphql-tools/merge": "8.4.1",
|
||||
"@graphql-tools/schema": "9.0.4",
|
||||
"@graphql-tools/utils": "8.12.0",
|
||||
"@graphql-yoga/node": "2.6.0",
|
||||
"@parse/fs-files-adapter": "1.2.2",
|
||||
"@parse/push-adapter": "4.2.0",
|
||||
"@parse/fs-files-adapter": "2.0.1",
|
||||
"@parse/push-adapter": "5.1.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"commander": "10.0.1",
|
||||
"cors": "2.8.5",
|
||||
"deepcopy": "2.1.0",
|
||||
"express": "4.18.2",
|
||||
"express": "4.21.1",
|
||||
"express-rate-limit": "6.7.0",
|
||||
"follow-redirects": "1.15.2",
|
||||
"follow-redirects": "1.15.6",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-relay": "0.10.0",
|
||||
"graphql-list-fields": "2.0.4",
|
||||
"graphql-relay": "0.10.1",
|
||||
"graphql-tag": "2.12.6",
|
||||
"intersect": "1.0.1",
|
||||
"ip-range-check": "0.2.0",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"jwks-rsa": "2.1.5",
|
||||
"jwks-rsa": "3.1.0",
|
||||
"ldapjs": "2.3.3",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "9.1.1",
|
||||
"lru-cache": "10.1.0",
|
||||
"mime": "3.0.0",
|
||||
"mongodb": "4.10.0",
|
||||
"mustache": "4.2.0",
|
||||
"otpauth": "9.1.2",
|
||||
"parse": "4.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"otpauth": "9.2.2",
|
||||
"parse": "4.2.0",
|
||||
"path-to-regexp": "6.2.2",
|
||||
"pg-monitor": "2.0.0",
|
||||
"pg-promise": "11.5.0",
|
||||
"pg-promise": "11.5.4",
|
||||
"pluralize": "8.0.0",
|
||||
"rate-limit-redis": "3.0.2",
|
||||
"redis": "4.6.6",
|
||||
"semver": "7.5.2",
|
||||
"redis": "4.6.13",
|
||||
"semver": "7.5.4",
|
||||
"subscriptions-transport-ws": "0.11.0",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "9.0.0",
|
||||
"winston": "3.8.2",
|
||||
"winston-daily-rotate-file": "4.7.1",
|
||||
"ws": "8.13.0"
|
||||
"uuid": "9.0.1",
|
||||
"winston": "3.12.0",
|
||||
"winston-daily-rotate-file": "5.0.0",
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "1.9.1",
|
||||
@@ -125,8 +124,9 @@
|
||||
"test:mongodb:4.4.13": "npm run test:mongodb --dbversion=4.4.13",
|
||||
"test:mongodb:5.3.2": "npm run test:mongodb --dbversion=5.3.2",
|
||||
"test:mongodb:6.0.2": "npm run test:mongodb --dbversion=6.0.2",
|
||||
"test:postgres:testonly": "cross-env PARSE_SERVER_TEST_DB=postgres PARSE_SERVER_TEST_DATABASE_URI=postgres://postgres:password@localhost:5432/parse_server_postgres_adapter_test_database npm run testonly",
|
||||
"posttest:mongodb": "mongodb-runner stop",
|
||||
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start",
|
||||
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=6.0.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start",
|
||||
"testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine",
|
||||
"test": "npm run testonly",
|
||||
"posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner stop",
|
||||
|
||||
+4
-15
@@ -24,11 +24,11 @@ const templates = {
|
||||
async function config() {
|
||||
|
||||
// Get branch
|
||||
const branch = ref.split('/').pop().split('-')[0];
|
||||
const branch = ref.split('/').pop();
|
||||
console.log(`Running on branch: ${branch}`);
|
||||
|
||||
// Set changelog file
|
||||
const changelogFile = `./changelogs/CHANGELOG_${branch}.md`;
|
||||
const changelogFile = `./changelogs/CHANGELOG_release.md`;
|
||||
console.log(`Changelog file output to: ${changelogFile}`);
|
||||
|
||||
// Load template file contents
|
||||
@@ -39,9 +39,8 @@ async function config() {
|
||||
'release',
|
||||
{ name: 'alpha', prerelease: true },
|
||||
{ name: 'beta', prerelease: true },
|
||||
'next-major',
|
||||
// Long-Term-Support branches; defined as GLOB pattern
|
||||
'release-+([0-9]).x.x',
|
||||
// Long-Term-Support branches
|
||||
{ name: 'release-6.x.x', range: '6.x.x', channel: '6.x.x' },
|
||||
],
|
||||
dryRun: false,
|
||||
debug: true,
|
||||
@@ -85,16 +84,6 @@ async function config() {
|
||||
labels: ['type:ci'],
|
||||
releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
|
||||
}],
|
||||
// Back-merge module runs last because if it fails it should not impede the release process
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"branches": [
|
||||
{ from: "beta", to: "alpha" },
|
||||
{ from: "release", to: "beta" },
|
||||
]
|
||||
}
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"equal": true,
|
||||
"expectAsync": true,
|
||||
"notEqual": true,
|
||||
"it_id": true,
|
||||
"it_only_db": true,
|
||||
"it_only_mongodb_version": true,
|
||||
"it_only_postgres_version": true,
|
||||
|
||||
@@ -2081,7 +2081,7 @@ describe('facebook limited auth adapter', () => {
|
||||
|
||||
it('should use algorithm from key header to verify id_token', async () => {
|
||||
const fakeClaim = {
|
||||
iss: 'https://facebook.com',
|
||||
iss: 'https://www.facebook.com',
|
||||
aud: 'secret',
|
||||
exp: Date.now(),
|
||||
sub: 'the_user_id',
|
||||
@@ -2145,7 +2145,7 @@ describe('facebook limited auth adapter', () => {
|
||||
|
||||
it('(using client id as string) should verify id_token', async () => {
|
||||
const fakeClaim = {
|
||||
iss: 'https://facebook.com',
|
||||
iss: 'https://www.facebook.com',
|
||||
aud: 'secret',
|
||||
exp: Date.now(),
|
||||
sub: 'the_user_id',
|
||||
@@ -2172,7 +2172,7 @@ describe('facebook limited auth adapter', () => {
|
||||
|
||||
it('(using client id as array) should verify id_token', async () => {
|
||||
const fakeClaim = {
|
||||
iss: 'https://facebook.com',
|
||||
iss: 'https://www.facebook.com',
|
||||
aud: 'secret',
|
||||
exp: Date.now(),
|
||||
sub: 'the_user_id',
|
||||
@@ -2199,7 +2199,7 @@ describe('facebook limited auth adapter', () => {
|
||||
|
||||
it('(using client id as array with multiple items) should verify id_token', async () => {
|
||||
const fakeClaim = {
|
||||
iss: 'https://facebook.com',
|
||||
iss: 'https://www.facebook.com',
|
||||
aud: 'secret',
|
||||
exp: Date.now(),
|
||||
sub: 'the_user_id',
|
||||
@@ -2250,7 +2250,7 @@ describe('facebook limited auth adapter', () => {
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'id token not issued by correct OpenID provider - expected: https://facebook.com | from: https://not.facebook.com'
|
||||
'id token not issued by correct OpenID provider - expected: https://www.facebook.com | from: https://not.facebook.com'
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -2286,7 +2286,7 @@ describe('facebook limited auth adapter', () => {
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'id token not issued by correct OpenID provider - expected: https://facebook.com | from: https://not.facebook.com'
|
||||
'id token not issued by correct OpenID provider - expected: https://www.facebook.com | from: https://not.facebook.com'
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -2320,7 +2320,7 @@ describe('facebook limited auth adapter', () => {
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(
|
||||
'id token not issued by correct OpenID provider - expected: https://facebook.com | from: https://not.facebook.com'
|
||||
'id token not issued by correct OpenID provider - expected: https://www.facebook.com | from: https://not.facebook.com'
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -2378,7 +2378,7 @@ describe('facebook limited auth adapter', () => {
|
||||
|
||||
it('should throw error with with invalid user id', async () => {
|
||||
const fakeClaim = {
|
||||
iss: 'https://facebook.com',
|
||||
iss: 'https://www.facebook.com',
|
||||
aud: 'invalid_client_id',
|
||||
sub: 'a_different_user_id',
|
||||
};
|
||||
|
||||
@@ -2510,6 +2510,31 @@ describe('beforeFind hooks', () => {
|
||||
expect(res2.get('pointerFieldArray')[0].get('aField')).toBe('aFieldValue');
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should have access to context in include query in beforeFind hook', async () => {
|
||||
let beforeFindTestObjectCalled = false;
|
||||
let beforeFindTestObject2Called = false;
|
||||
const obj1 = new Parse.Object('TestObject');
|
||||
const obj2 = new Parse.Object('TestObject2');
|
||||
obj2.set('aField', 'aFieldValue');
|
||||
await obj2.save();
|
||||
obj1.set('pointerField', obj2);
|
||||
await obj1.save();
|
||||
Parse.Cloud.beforeFind('TestObject', req => {
|
||||
expect(req.context).toBeDefined();
|
||||
expect(req.context.a).toEqual('a');
|
||||
beforeFindTestObjectCalled = true;
|
||||
});
|
||||
Parse.Cloud.beforeFind('TestObject2', req => {
|
||||
expect(req.context).toBeDefined();
|
||||
expect(req.context.a).toEqual('a');
|
||||
beforeFindTestObject2Called = true;
|
||||
});
|
||||
const query = new Parse.Query('TestObject');
|
||||
await query.include('pointerField').find({ context: { a: 'a' } });
|
||||
expect(beforeFindTestObjectCalled).toBeTrue();
|
||||
expect(beforeFindTestObject2Called).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterFind hooks', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const Config = require('../lib/Config');
|
||||
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
|
||||
const validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
@@ -361,6 +362,259 @@ describe('DatabaseController', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('enableCollationCaseComparison', () => {
|
||||
const dummyStorageAdapter = {
|
||||
find: () => Promise.resolve([]),
|
||||
watch: () => Promise.resolve(),
|
||||
getAllClasses: () => Promise.resolve([]),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Config.get(Parse.applicationId).schemaCache.clear();
|
||||
});
|
||||
|
||||
it('should force caseInsensitive to false with enableCollationCaseComparison option', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {
|
||||
enableCollationCaseComparison: true,
|
||||
});
|
||||
const spy = spyOn(dummyStorageAdapter, 'find');
|
||||
spy.and.callThrough();
|
||||
await databaseController.find('SomeClass', {}, { caseInsensitive: true });
|
||||
expect(spy.calls.all()[0].args[3].caseInsensitive).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support caseInsensitive without enableCollationCaseComparison option', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {});
|
||||
const spy = spyOn(dummyStorageAdapter, 'find');
|
||||
spy.and.callThrough();
|
||||
await databaseController.find('_User', {}, { caseInsensitive: true });
|
||||
expect(spy.calls.all()[0].args[3].caseInsensitive).toEqual(true);
|
||||
});
|
||||
|
||||
it_only_db('mongo')(
|
||||
'should create insensitive indexes without enableCollationCaseComparison',
|
||||
async () => {
|
||||
await reconfigureServer({
|
||||
databaseURI: 'mongodb://localhost:27017/enableCollationCaseComparisonFalse',
|
||||
databaseAdapter: undefined,
|
||||
});
|
||||
const user = new Parse.User();
|
||||
await user.save({
|
||||
username: 'example',
|
||||
password: 'password',
|
||||
email: 'example@example.com',
|
||||
});
|
||||
const schemas = await Parse.Schema.all();
|
||||
const UserSchema = schemas.find(({ className }) => className === '_User');
|
||||
expect(UserSchema.indexes).toEqual({
|
||||
_id_: { _id: 1 },
|
||||
username_1: { username: 1 },
|
||||
case_insensitive_username: { username: 1 },
|
||||
case_insensitive_email: { email: 1 },
|
||||
email_1: { email: 1 },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it_only_db('mongo')(
|
||||
'should not create insensitive indexes with enableCollationCaseComparison',
|
||||
async () => {
|
||||
await reconfigureServer({
|
||||
enableCollationCaseComparison: true,
|
||||
databaseURI: 'mongodb://localhost:27017/enableCollationCaseComparisonTrue',
|
||||
databaseAdapter: undefined,
|
||||
});
|
||||
const user = new Parse.User();
|
||||
await user.save({
|
||||
username: 'example',
|
||||
password: 'password',
|
||||
email: 'example@example.com',
|
||||
});
|
||||
const schemas = await Parse.Schema.all();
|
||||
const UserSchema = schemas.find(({ className }) => className === '_User');
|
||||
expect(UserSchema.indexes).toEqual({
|
||||
_id_: { _id: 1 },
|
||||
username_1: { username: 1 },
|
||||
email_1: { email: 1 },
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('convertEmailToLowercase', () => {
|
||||
const dummyStorageAdapter = {
|
||||
createObject: () => Promise.resolve({ ops: [{}] }),
|
||||
findOneAndUpdate: () => Promise.resolve({}),
|
||||
watch: () => Promise.resolve(),
|
||||
getAllClasses: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
className: '_User',
|
||||
fields: { email: 'String' },
|
||||
indexes: {},
|
||||
classLevelPermissions: { protectedFields: {} },
|
||||
},
|
||||
]),
|
||||
};
|
||||
const dates = {
|
||||
createdAt: { iso: undefined, __type: 'Date' },
|
||||
updatedAt: { iso: undefined, __type: 'Date' },
|
||||
};
|
||||
|
||||
it('should not transform email to lower case without convertEmailToLowercase option on create', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {});
|
||||
const spy = spyOn(dummyStorageAdapter, 'createObject');
|
||||
spy.and.callThrough();
|
||||
await databaseController.create('_User', {
|
||||
email: 'EXAMPLE@EXAMPLE.COM',
|
||||
});
|
||||
expect(spy.calls.all()[0].args[2]).toEqual({
|
||||
email: 'EXAMPLE@EXAMPLE.COM',
|
||||
...dates,
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform email to lower case with convertEmailToLowercase option on create', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {
|
||||
convertEmailToLowercase: true,
|
||||
});
|
||||
const spy = spyOn(dummyStorageAdapter, 'createObject');
|
||||
spy.and.callThrough();
|
||||
await databaseController.create('_User', {
|
||||
email: 'EXAMPLE@EXAMPLE.COM',
|
||||
});
|
||||
expect(spy.calls.all()[0].args[2]).toEqual({
|
||||
email: 'example@example.com',
|
||||
...dates,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not transform email to lower case without convertEmailToLowercase option on update', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {});
|
||||
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
|
||||
spy.and.callThrough();
|
||||
await databaseController.update('_User', { id: 'example' }, { email: 'EXAMPLE@EXAMPLE.COM' });
|
||||
expect(spy.calls.all()[0].args[3]).toEqual({
|
||||
email: 'EXAMPLE@EXAMPLE.COM',
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform email to lower case with convertEmailToLowercase option on update', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {
|
||||
convertEmailToLowercase: true,
|
||||
});
|
||||
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
|
||||
spy.and.callThrough();
|
||||
await databaseController.update('_User', { id: 'example' }, { email: 'EXAMPLE@EXAMPLE.COM' });
|
||||
expect(spy.calls.all()[0].args[3]).toEqual({
|
||||
email: 'example@example.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not find a case insensitive user by email with convertEmailToLowercase', async () => {
|
||||
await reconfigureServer({ convertEmailToLowercase: true });
|
||||
const user = new Parse.User();
|
||||
await user.save({ username: 'EXAMPLE', email: 'EXAMPLE@EXAMPLE.COM', password: 'password' });
|
||||
|
||||
const query = new Parse.Query(Parse.User);
|
||||
query.equalTo('email', 'EXAMPLE@EXAMPLE.COM');
|
||||
const result = await query.find({ useMasterKey: true });
|
||||
expect(result.length).toEqual(0);
|
||||
|
||||
const query2 = new Parse.Query(Parse.User);
|
||||
query2.equalTo('email', 'example@example.com');
|
||||
const result2 = await query2.find({ useMasterKey: true });
|
||||
expect(result2.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertUsernameToLowercase', () => {
|
||||
const dummyStorageAdapter = {
|
||||
createObject: () => Promise.resolve({ ops: [{}] }),
|
||||
findOneAndUpdate: () => Promise.resolve({}),
|
||||
watch: () => Promise.resolve(),
|
||||
getAllClasses: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
className: '_User',
|
||||
fields: { username: 'String' },
|
||||
indexes: {},
|
||||
classLevelPermissions: { protectedFields: {} },
|
||||
},
|
||||
]),
|
||||
};
|
||||
const dates = {
|
||||
createdAt: { iso: undefined, __type: 'Date' },
|
||||
updatedAt: { iso: undefined, __type: 'Date' },
|
||||
};
|
||||
|
||||
it('should not transform username to lower case without convertUsernameToLowercase option on create', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {});
|
||||
const spy = spyOn(dummyStorageAdapter, 'createObject');
|
||||
spy.and.callThrough();
|
||||
await databaseController.create('_User', {
|
||||
username: 'EXAMPLE',
|
||||
});
|
||||
expect(spy.calls.all()[0].args[2]).toEqual({
|
||||
username: 'EXAMPLE',
|
||||
...dates,
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform username to lower case with convertUsernameToLowercase option on create', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {
|
||||
convertUsernameToLowercase: true,
|
||||
});
|
||||
const spy = spyOn(dummyStorageAdapter, 'createObject');
|
||||
spy.and.callThrough();
|
||||
await databaseController.create('_User', {
|
||||
username: 'EXAMPLE',
|
||||
});
|
||||
expect(spy.calls.all()[0].args[2]).toEqual({
|
||||
username: 'example',
|
||||
...dates,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not transform username to lower case without convertUsernameToLowercase option on update', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {});
|
||||
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
|
||||
spy.and.callThrough();
|
||||
await databaseController.update('_User', { id: 'example' }, { username: 'EXAMPLE' });
|
||||
expect(spy.calls.all()[0].args[3]).toEqual({
|
||||
username: 'EXAMPLE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform username to lower case with convertUsernameToLowercase option on update', async () => {
|
||||
const databaseController = new DatabaseController(dummyStorageAdapter, {
|
||||
convertUsernameToLowercase: true,
|
||||
});
|
||||
const spy = spyOn(dummyStorageAdapter, 'findOneAndUpdate');
|
||||
spy.and.callThrough();
|
||||
await databaseController.update('_User', { id: 'example' }, { username: 'EXAMPLE' });
|
||||
expect(spy.calls.all()[0].args[3]).toEqual({
|
||||
username: 'example',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not find a case insensitive user by username with convertUsernameToLowercase', async () => {
|
||||
await reconfigureServer({ convertUsernameToLowercase: true });
|
||||
const user = new Parse.User();
|
||||
await user.save({ username: 'EXAMPLE', password: 'password' });
|
||||
|
||||
const query = new Parse.Query(Parse.User);
|
||||
query.equalTo('username', 'EXAMPLE');
|
||||
const result = await query.find({ useMasterKey: true });
|
||||
expect(result.length).toEqual(0);
|
||||
|
||||
const query2 = new Parse.Query(Parse.User);
|
||||
query2.equalTo('username', 'example');
|
||||
const result2 = await query2.find({ useMasterKey: true });
|
||||
expect(result2.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildCLP(pointerNames) {
|
||||
|
||||
+105
-20
@@ -1,10 +1,19 @@
|
||||
const middlewares = require('../lib/middlewares');
|
||||
const AppCache = require('../lib/cache').AppCache;
|
||||
const { BlockList } = require('net');
|
||||
|
||||
const AppCachePut = (appId, config) =>
|
||||
AppCache.put(appId, {
|
||||
...config,
|
||||
maintenanceKeyIpsStore: new Map(),
|
||||
masterKeyIpsStore: new Map(),
|
||||
});
|
||||
|
||||
describe('middlewares', () => {
|
||||
let fakeReq, fakeRes;
|
||||
beforeEach(() => {
|
||||
fakeReq = {
|
||||
ip: '127.0.0.1',
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: {
|
||||
@@ -16,7 +25,7 @@ describe('middlewares', () => {
|
||||
},
|
||||
};
|
||||
fakeRes = jasmine.createSpyObj('fakeRes', ['end', 'status']);
|
||||
AppCache.put(fakeReq.body._ApplicationId, {});
|
||||
AppCachePut(fakeReq.body._ApplicationId, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -35,7 +44,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but no key supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
@@ -44,7 +53,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but supplied key is incorrect', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
@@ -54,7 +63,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should give invalid response when keys are configured but different key is supplied', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey',
|
||||
});
|
||||
@@ -64,7 +73,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should succeed when any one of the configured keys supplied', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
clientKey: 'clientKey',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey',
|
||||
@@ -77,7 +86,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should succeed when client key supplied but empty', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
clientKey: '',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey',
|
||||
@@ -90,7 +99,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
});
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
@@ -117,7 +126,7 @@ describe('middlewares', () => {
|
||||
otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey'
|
||||
);
|
||||
it(`it should pull ${bodyKey} into req.info`, done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKeyIps: ['0.0.0.0/0'],
|
||||
});
|
||||
fakeReq.ip = '127.0.0.1';
|
||||
@@ -138,7 +147,7 @@ describe('middlewares', () => {
|
||||
it('should not succeed and log if the ip does not belong to masterKeyIps list', async () => {
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callFake(() => {});
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['10.0.0.1'],
|
||||
});
|
||||
@@ -152,7 +161,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should not succeed if the ip does not belong to masterKeyIps list', async () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['10.0.0.1'],
|
||||
});
|
||||
@@ -165,7 +174,7 @@ describe('middlewares', () => {
|
||||
it('should not succeed if the ip does not belong to maintenanceKeyIps list', async () => {
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callFake(() => {});
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
maintenanceKey: 'masterKey',
|
||||
maintenanceKeyIps: ['10.0.0.0', '10.0.0.1'],
|
||||
});
|
||||
@@ -179,7 +188,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should succeed if the ip does belong to masterKeyIps list', async () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['10.0.0.1'],
|
||||
});
|
||||
@@ -190,7 +199,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should allow any ip to use masterKey if masterKeyIps is empty', async () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
masterKeyIps: ['0.0.0.0/0'],
|
||||
});
|
||||
@@ -221,7 +230,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should set default Access-Control-Allow-Headers if allowHeaders are empty', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
allowHeaders: undefined,
|
||||
});
|
||||
const headers = {};
|
||||
@@ -234,7 +243,7 @@ describe('middlewares', () => {
|
||||
allowCrossDomain(fakeReq, res, () => {});
|
||||
expect(headers['Access-Control-Allow-Headers']).toContain(middlewares.DEFAULT_ALLOWED_HEADERS);
|
||||
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
allowHeaders: [],
|
||||
});
|
||||
allowCrossDomain(fakeReq, res, () => {});
|
||||
@@ -242,7 +251,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should append custom headers to Access-Control-Allow-Headers if allowHeaders provided', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
allowHeaders: ['Header-1', 'Header-2'],
|
||||
});
|
||||
const headers = {};
|
||||
@@ -258,7 +267,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should set default Access-Control-Allow-Origin if allowOrigin is empty', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
allowOrigin: undefined,
|
||||
});
|
||||
const headers = {};
|
||||
@@ -273,7 +282,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should set custom origin to Access-Control-Allow-Origin if allowOrigin is provided', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
allowOrigin: 'https://parseplatform.org/',
|
||||
});
|
||||
const headers = {};
|
||||
@@ -317,7 +326,7 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should use user provided on field userFromJWT', done => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
});
|
||||
fakeReq.userFromJWT = 'fake-user';
|
||||
@@ -328,11 +337,87 @@ describe('middlewares', () => {
|
||||
});
|
||||
|
||||
it('should give invalid response when upload file without x-parse-application-id in header', () => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey',
|
||||
});
|
||||
fakeReq.body = Buffer.from('fake-file');
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should match address', () => {
|
||||
const ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
|
||||
const anotherIpv6 = '::ffff:101.10.0.1';
|
||||
const ipv4 = '192.168.0.101';
|
||||
const localhostV6 = '::1';
|
||||
const localhostV62 = '::ffff:127.0.0.1';
|
||||
const localhostV4 = '127.0.0.1';
|
||||
|
||||
const v6 = [ipv6, anotherIpv6];
|
||||
v6.forEach(ip => {
|
||||
expect(middlewares.checkIp(ip, ['::/0'], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ip, ['::'], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ip, ['0.0.0.0'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ip, ['0.0.0.0/0'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ip, ['123.123.123.123'], new Map())).toBe(false);
|
||||
});
|
||||
|
||||
expect(middlewares.checkIp(ipv6, [anotherIpv6], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ipv6, [ipv6], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ipv6, ['2001:db8:85a3:0:0:8a2e:0:0/100'], new Map())).toBe(true);
|
||||
|
||||
expect(middlewares.checkIp(ipv4, ['::'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ipv4, ['::/0'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ipv4, ['0.0.0.0'], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ipv4, ['0.0.0.0/0'], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ipv4, ['123.123.123.123'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(ipv4, [ipv4], new Map())).toBe(true);
|
||||
expect(middlewares.checkIp(ipv4, ['192.168.0.0/24'], new Map())).toBe(true);
|
||||
|
||||
expect(middlewares.checkIp(localhostV4, ['::1'], new Map())).toBe(false);
|
||||
expect(middlewares.checkIp(localhostV6, ['::1'], new Map())).toBe(true);
|
||||
// ::ffff:127.0.0.1 is a padded ipv4 address but not ::1
|
||||
expect(middlewares.checkIp(localhostV62, ['::1'], new Map())).toBe(false);
|
||||
// ::ffff:127.0.0.1 is a padded ipv4 address and is a match for 127.0.0.1
|
||||
expect(middlewares.checkIp(localhostV62, ['127.0.0.1'], new Map())).toBe(true);
|
||||
});
|
||||
|
||||
it('should match address with cache', () => {
|
||||
const ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
|
||||
const cache1 = new Map();
|
||||
const spyBlockListCheck = spyOn(BlockList.prototype, 'check').and.callThrough();
|
||||
expect(middlewares.checkIp(ipv6, ['::'], cache1)).toBe(true);
|
||||
expect(cache1.get('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(undefined);
|
||||
expect(cache1.get('allowAllIpv6')).toBe(true);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(0);
|
||||
|
||||
const cache2 = new Map();
|
||||
expect(middlewares.checkIp('::1', ['::1'], cache2)).toBe(true);
|
||||
expect(cache2.get('::1')).toBe(true);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
expect(middlewares.checkIp('::1', ['::1'], cache2)).toBe(true);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
spyBlockListCheck.calls.reset();
|
||||
|
||||
const cache3 = new Map();
|
||||
expect(middlewares.checkIp('127.0.0.1', ['127.0.0.1'], cache3)).toBe(true);
|
||||
expect(cache3.get('127.0.0.1')).toBe(true);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
expect(middlewares.checkIp('127.0.0.1', ['127.0.0.1'], cache3)).toBe(true);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
spyBlockListCheck.calls.reset();
|
||||
|
||||
const cache4 = new Map();
|
||||
const ranges = ['127.0.0.1', '192.168.0.0/24'];
|
||||
// should not cache negative match
|
||||
expect(middlewares.checkIp('123.123.123.123', ranges, cache4)).toBe(false);
|
||||
expect(cache4.get('123.123.123.123')).toBe(undefined);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
spyBlockListCheck.calls.reset();
|
||||
|
||||
// should not cache cidr
|
||||
expect(middlewares.checkIp('192.168.0.101', ranges, cache4)).toBe(true);
|
||||
expect(cache4.get('192.168.0.101')).toBe(undefined);
|
||||
expect(spyBlockListCheck).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,6 +254,61 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
expect(obj.get('foo').test.date[0] instanceof Date).toBeTrue();
|
||||
});
|
||||
|
||||
it('upserts with $setOnInsert', async () => {
|
||||
const uuid = require('uuid');
|
||||
const uuid1 = uuid.v4();
|
||||
const uuid2 = uuid.v4();
|
||||
const schema = {
|
||||
className: 'MyClass',
|
||||
fields: {
|
||||
x: { type: 'Number' },
|
||||
count: { type: 'Number' },
|
||||
},
|
||||
classLevelPermissions: {},
|
||||
};
|
||||
|
||||
const myClassSchema = new Parse.Schema(schema.className);
|
||||
myClassSchema.setCLP(schema.classLevelPermissions);
|
||||
await myClassSchema.save();
|
||||
|
||||
const query = {
|
||||
x: 1,
|
||||
};
|
||||
const update = {
|
||||
objectId: {
|
||||
__op: 'SetOnInsert',
|
||||
amount: uuid1,
|
||||
},
|
||||
count: {
|
||||
__op: 'Increment',
|
||||
amount: 1,
|
||||
},
|
||||
};
|
||||
await Parse.Server.database.update(
|
||||
'MyClass',
|
||||
query,
|
||||
update,
|
||||
{ upsert: true },
|
||||
);
|
||||
update.objectId.amount = uuid2;
|
||||
await Parse.Server.database.update(
|
||||
'MyClass',
|
||||
query,
|
||||
update,
|
||||
{ upsert: true },
|
||||
);
|
||||
|
||||
const res = await Parse.Server.database.find(
|
||||
schema.className,
|
||||
{},
|
||||
{},
|
||||
);
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].objectId).toBe(uuid1);
|
||||
expect(res[0].count).toBe(2);
|
||||
expect(res[0].x).toBe(1);
|
||||
});
|
||||
|
||||
it('handles updating a single object with array, object date', done => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ describe('OAuth', function () {
|
||||
done();
|
||||
}
|
||||
|
||||
it('GET request for a resource that requires OAuth should fail with invalid credentials', done => {
|
||||
xit('GET request for a resource that requires OAuth should fail with invalid credentials', done => {
|
||||
/*
|
||||
This endpoint has been chosen to make a request to an endpoint that requires OAuth which fails due to missing authentication.
|
||||
Any other endpoint from the Twitter API that requires OAuth can be used instead in case the currently used endpoint deprecates.
|
||||
@@ -105,7 +105,7 @@ describe('OAuth', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('POST request for a resource that requires OAuth should fail with invalid credentials', done => {
|
||||
xit('POST request for a resource that requires OAuth should fail with invalid credentials', done => {
|
||||
/*
|
||||
This endpoint has been chosen to make a request to an endpoint that requires OAuth which fails due to missing authentication.
|
||||
Any other endpoint from the Twitter API that requires OAuth can be used instead in case the currently used endpoint deprecates.
|
||||
|
||||
@@ -1267,6 +1267,24 @@ describe('miscellaneous', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('test cloud function query parameters with array of pointers', async () => {
|
||||
Parse.Cloud.define('echoParams', req => {
|
||||
return req.params;
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
};
|
||||
const response = await request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/functions/echoParams',
|
||||
body: '{"arr": [{ "__type": "Pointer", "className": "PointerTest" }]}',
|
||||
});
|
||||
const res = response.data.result;
|
||||
expect(res.arr.length).toEqual(1);
|
||||
});
|
||||
it('can handle null params in cloud functions (regression test for #1742)', done => {
|
||||
Parse.Cloud.define('func', request => {
|
||||
expect(request.params.nullParam).toEqual(null);
|
||||
|
||||
@@ -1432,6 +1432,34 @@ describe('Parse.File testing', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('allows file without extension', async () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
enableForPublic: true,
|
||||
fileExtensions: ['^[^hH][^tT][^mM][^lL]?$'],
|
||||
},
|
||||
});
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
|
||||
const values = ['filenamewithoutextension'];
|
||||
|
||||
for (const value of values) {
|
||||
await expectAsync(
|
||||
request({
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
url: `http://localhost:8378/1/files/${value}`,
|
||||
body: '<html></html>\n',
|
||||
}).catch(e => {
|
||||
throw new Error(e.data.error);
|
||||
})
|
||||
).toBeResolved();
|
||||
}
|
||||
});
|
||||
|
||||
it('works with array', async () => {
|
||||
await reconfigureServer({
|
||||
fileUpload: {
|
||||
|
||||
@@ -694,3 +694,36 @@ describe('triggers', () => {
|
||||
expect(req.context).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizing names', () => {
|
||||
const invalidNames = [
|
||||
`test'%3bdeclare%20@q%20varchar(99)%3bset%20@q%3d'%5c%5cxxxxxxxxxxxxxxx.yyyyy'%2b'fy.com%5cxus'%3b%20exec%20master.dbo.xp_dirtree%20@q%3b--%20`,
|
||||
`test.function.name`,
|
||||
];
|
||||
|
||||
it('should not crash server and return error on invalid Cloud Function name', async () => {
|
||||
for (const invalidName of invalidNames) {
|
||||
let error;
|
||||
try {
|
||||
await Parse.Cloud.run(invalidName);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toMatch(/Invalid function/);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not crash server and return error on invalid Cloud Job name', async () => {
|
||||
for (const invalidName of invalidNames) {
|
||||
let error;
|
||||
try {
|
||||
await Parse.Cloud.startJob(invalidName);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toMatch(/Invalid job/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+26
-2
@@ -103,6 +103,7 @@ const defaultConfiguration = {
|
||||
restAPIKey: 'rest',
|
||||
webhookKey: 'hook',
|
||||
masterKey: 'test',
|
||||
maintenanceKey: 'testing',
|
||||
readOnlyMasterKey: 'read-only-test',
|
||||
fileKey: 'test',
|
||||
directAccess: true,
|
||||
@@ -250,8 +251,8 @@ afterEach(function (done) {
|
||||
})
|
||||
.then(() => Parse.User.logOut())
|
||||
.then(
|
||||
() => {},
|
||||
() => {}
|
||||
() => { },
|
||||
() => { }
|
||||
) // swallow errors
|
||||
.then(() => {
|
||||
// Connection close events are not immediate on node 10+... wait a bit
|
||||
@@ -427,6 +428,29 @@ global.it_exclude_dbs = excluded => {
|
||||
}
|
||||
};
|
||||
|
||||
let testExclusionList = [];
|
||||
try {
|
||||
// Fetch test exclusion list
|
||||
testExclusionList = require('./testExclusionList.json');
|
||||
console.log(`Using test exclusion list with ${testExclusionList.length} entries`);
|
||||
} catch(error) {
|
||||
if(error.code !== 'MODULE_NOT_FOUND') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable test if its UUID is found in testExclusionList
|
||||
global.it_id = (id, func) => {
|
||||
if (testExclusionList.includes(id)) {
|
||||
return xit;
|
||||
} else {
|
||||
if(func === undefined)
|
||||
return it;
|
||||
else
|
||||
return func;
|
||||
}
|
||||
};
|
||||
|
||||
global.it_only_db = db => {
|
||||
if (
|
||||
process.env.PARSE_SERVER_TEST_DB === db ||
|
||||
|
||||
@@ -136,6 +136,119 @@ describe('rest create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with maintenance key', () => {
|
||||
let req;
|
||||
|
||||
async function getObject(id) {
|
||||
const res = await request({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
},
|
||||
method: 'GET',
|
||||
url: `http://localhost:8378/1/classes/TestObject/${id}`
|
||||
});
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Maintenance-Key': 'testing'
|
||||
},
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/classes/TestObject'
|
||||
};
|
||||
});
|
||||
|
||||
it('allows createdAt', async () => {
|
||||
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
|
||||
req.body = { createdAt };
|
||||
|
||||
const res = await request(req);
|
||||
expect(res.data.createdAt).toEqual(createdAt.iso);
|
||||
});
|
||||
|
||||
it('allows createdAt and updatedAt', async () => {
|
||||
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
|
||||
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
|
||||
req.body = { createdAt, updatedAt };
|
||||
|
||||
const res = await request(req);
|
||||
|
||||
const obj = await getObject(res.data.objectId);
|
||||
expect(obj.createdAt).toEqual(createdAt.iso);
|
||||
expect(obj.updatedAt).toEqual(updatedAt.iso);
|
||||
});
|
||||
|
||||
it('allows createdAt, updatedAt, and additional field', async () => {
|
||||
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
|
||||
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
|
||||
req.body = { createdAt, updatedAt, testing: 123 };
|
||||
|
||||
const res = await request(req);
|
||||
|
||||
const obj = await getObject(res.data.objectId);
|
||||
expect(obj.createdAt).toEqual(createdAt.iso);
|
||||
expect(obj.updatedAt).toEqual(updatedAt.iso);
|
||||
expect(obj.testing).toEqual(123);
|
||||
});
|
||||
|
||||
it('cannot set updatedAt dated before createdAt', async () => {
|
||||
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
|
||||
const updatedAt = { __type: 'Date', iso: '2018-12-01T00:00:00.000Z' };
|
||||
req.body = { createdAt, updatedAt };
|
||||
|
||||
try {
|
||||
await request(req);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
expect(err.data.code).toEqual(Parse.Error.VALIDATION_ERROR);
|
||||
}
|
||||
});
|
||||
|
||||
it('cannot set updatedAt without createdAt', async () => {
|
||||
const updatedAt = { __type: 'Date', iso: '2018-12-01T00:00:00.000Z' };
|
||||
req.body = { updatedAt };
|
||||
|
||||
const res = await request(req);
|
||||
|
||||
const obj = await getObject(res.data.objectId);
|
||||
expect(obj.updatedAt).not.toEqual(updatedAt.iso);
|
||||
});
|
||||
|
||||
it('handles bad types for createdAt and updatedAt', async () => {
|
||||
const createdAt = 12345;
|
||||
const updatedAt = true;
|
||||
req.body = { createdAt, updatedAt };
|
||||
|
||||
try {
|
||||
await request(req);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
expect(err.data.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
}
|
||||
});
|
||||
|
||||
it('cannot set createdAt or updatedAt without maintenance key', async () => {
|
||||
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
|
||||
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
|
||||
req.body = { createdAt, updatedAt };
|
||||
delete req.headers['X-Parse-Maintenance-Key'];
|
||||
|
||||
const res = await request(req);
|
||||
|
||||
expect(res.data.createdAt).not.toEqual(createdAt.iso);
|
||||
expect(res.data.updatedAt).not.toEqual(updatedAt.iso);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles array, object, date', done => {
|
||||
const now = new Date();
|
||||
const obj = {
|
||||
|
||||
@@ -1,6 +1,51 @@
|
||||
const request = require('../lib/request');
|
||||
|
||||
describe('Vulnerabilities', () => {
|
||||
describe('(GHSA-8xq9-g7ch-35hg) Custom object ID allows to acquire role privilege', () => {
|
||||
beforeAll(async () => {
|
||||
await reconfigureServer({ allowCustomObjectId: true });
|
||||
Parse.allowCustomObjectId = true;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await reconfigureServer({ allowCustomObjectId: false });
|
||||
Parse.allowCustomObjectId = false;
|
||||
});
|
||||
|
||||
it('denies user creation with poisoned object ID', async () => {
|
||||
await expectAsync(
|
||||
new Parse.User({ id: 'role:a', username: 'a', password: '123' }).save()
|
||||
).toBeRejectedWith(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.'));
|
||||
});
|
||||
|
||||
describe('existing sessions for users with poisoned object ID', () => {
|
||||
/** @type {Parse.User} */
|
||||
let poisonedUser;
|
||||
/** @type {Parse.User} */
|
||||
let innocentUser;
|
||||
|
||||
beforeAll(async () => {
|
||||
const parseServer = await global.reconfigureServer();
|
||||
const databaseController = parseServer.config.databaseController;
|
||||
[poisonedUser, innocentUser] = await Promise.all(
|
||||
['role:abc', 'abc'].map(async id => {
|
||||
// Create the users directly on the db to bypass the user creation check
|
||||
await databaseController.create('_User', { objectId: id });
|
||||
// Use the master key to create a session for them to bypass the session check
|
||||
return Parse.User.loginAs(id);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses session token of user with poisoned object ID', async () => {
|
||||
await expectAsync(
|
||||
new Parse.Query(Parse.User).find({ sessionToken: poisonedUser.getSessionToken() })
|
||||
).toBeRejectedWith(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.'));
|
||||
await new Parse.Query(Parse.User).find({ sessionToken: innocentUser.getSessionToken() });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Object prototype pollution', () => {
|
||||
it('denies object prototype to be polluted with keyword "constructor"', async () => {
|
||||
const headers = {
|
||||
@@ -459,3 +504,28 @@ describe('Vulnerabilities', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Postgres regex sanitizater', () => {
|
||||
it('sanitizes the regex correctly to prevent Injection', async () => {
|
||||
const user = new Parse.User();
|
||||
user.set('username', 'username');
|
||||
user.set('password', 'password');
|
||||
user.set('email', 'email@example.com');
|
||||
await user.signUp();
|
||||
|
||||
const response = await request({
|
||||
method: 'GET',
|
||||
url:
|
||||
"http://localhost:8378/1/classes/_User?where[username][$regex]=A'B'%3BSELECT+PG_SLEEP(3)%3B--",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data.results).toEqual(jasmine.any(Array));
|
||||
expect(response.data.results.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const jwt = require('jsonwebtoken');
|
||||
const httpsRequest = require('./httpsRequest');
|
||||
const authUtils = require('./utils');
|
||||
|
||||
const TOKEN_ISSUER = 'https://facebook.com';
|
||||
const TOKEN_ISSUER = 'https://www.facebook.com';
|
||||
|
||||
function getAppSecretPath(authData, options = {}) {
|
||||
const appSecret = options.appSecret;
|
||||
|
||||
@@ -986,6 +986,13 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) {
|
||||
return { __op: '$inc', arg: amount };
|
||||
}
|
||||
|
||||
case 'SetOnInsert':
|
||||
if (flatten) {
|
||||
return amount;
|
||||
} else {
|
||||
return { __op: '$setOnInsert', arg: amount };
|
||||
}
|
||||
|
||||
case 'Add':
|
||||
case 'AddUnique':
|
||||
if (!(objects instanceof Array)) {
|
||||
|
||||
@@ -2614,16 +2614,16 @@ function isAnyValueRegexStartsWith(values) {
|
||||
});
|
||||
}
|
||||
|
||||
function createLiteralRegex(remaining) {
|
||||
function createLiteralRegex(remaining: string) {
|
||||
return remaining
|
||||
.split('')
|
||||
.map(c => {
|
||||
const regex = RegExp('[0-9 ]|\\p{L}', 'u'); // Support all unicode letter chars
|
||||
const regex = RegExp('[0-9 ]|\\p{L}', 'u'); // Support all Unicode letter chars
|
||||
if (c.match(regex) !== null) {
|
||||
// don't escape alphanumeric characters
|
||||
// Don't escape alphanumeric characters
|
||||
return c;
|
||||
}
|
||||
// escape everything else (single quotes with single quotes, everything else with a backslash)
|
||||
// Escape everything else (single quotes with single quotes, everything else with a backslash)
|
||||
return c === `'` ? `''` : `\\${c}`;
|
||||
})
|
||||
.join('');
|
||||
@@ -2633,14 +2633,14 @@ function literalizeRegexPart(s: string) {
|
||||
const matcher1 = /\\Q((?!\\E).*)\\E$/;
|
||||
const result1: any = s.match(matcher1);
|
||||
if (result1 && result1.length > 1 && result1.index > -1) {
|
||||
// process regex that has a beginning and an end specified for the literal text
|
||||
// Process Regex that has a beginning and an end specified for the literal text
|
||||
const prefix = s.substring(0, result1.index);
|
||||
const remaining = result1[1];
|
||||
|
||||
return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
|
||||
}
|
||||
|
||||
// process regex that has a beginning specified for the literal text
|
||||
// Process Regex that has a beginning specified for the literal text
|
||||
const matcher2 = /\\Q((?!\\E).*)$/;
|
||||
const result2: any = s.match(matcher2);
|
||||
if (result2 && result2.length > 1 && result2.index > -1) {
|
||||
@@ -2650,14 +2650,18 @@ function literalizeRegexPart(s: string) {
|
||||
return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
|
||||
}
|
||||
|
||||
// remove all instances of \Q and \E from the remaining text & escape single quotes
|
||||
// Remove problematic chars from remaining text
|
||||
return s
|
||||
// Remove all instances of \Q and \E
|
||||
.replace(/([^\\])(\\E)/, '$1')
|
||||
.replace(/([^\\])(\\Q)/, '$1')
|
||||
.replace(/^\\E/, '')
|
||||
.replace(/^\\Q/, '')
|
||||
.replace(/([^'])'/, `$1''`)
|
||||
.replace(/^'([^'])/, `''$1`);
|
||||
// Ensure even number of single quote sequences by adding an extra single quote if needed;
|
||||
// this ensures that every single quote is escaped
|
||||
.replace(/'+/g, match => {
|
||||
return match.length % 2 === 0 ? match : match + "'";
|
||||
});
|
||||
}
|
||||
|
||||
var GeoPointCoder = {
|
||||
|
||||
@@ -173,6 +173,11 @@ const getAuthForSessionToken = async function ({
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
|
||||
}
|
||||
const obj = session.user;
|
||||
|
||||
if (typeof obj['objectId'] === 'string' && obj['objectId'].startsWith('role:')) {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.');
|
||||
}
|
||||
|
||||
delete obj.password;
|
||||
obj['className'] = '_User';
|
||||
obj['sessionToken'] = sessionToken;
|
||||
|
||||
@@ -279,6 +279,9 @@ const flattenUpdateOperatorsForCreate = object => {
|
||||
}
|
||||
object[key] = object[key].amount;
|
||||
break;
|
||||
case 'SetOnInsert':
|
||||
object[key] = object[key].amount;
|
||||
break;
|
||||
case 'Add':
|
||||
if (!(object[key].objects instanceof Array)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
|
||||
@@ -365,6 +368,22 @@ const relationSchema = {
|
||||
fields: { relatedId: { type: 'String' }, owningId: { type: 'String' } },
|
||||
};
|
||||
|
||||
const convertEmailToLowercase = (object, className, options) => {
|
||||
if (className === '_User' && options.convertEmailToLowercase) {
|
||||
if (typeof object['email'] === 'string') {
|
||||
object['email'] = object['email'].toLowerCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const convertUsernameToLowercase = (object, className, options) => {
|
||||
if (className === '_User' && options.convertUsernameToLowercase) {
|
||||
if (typeof object['username'] === 'string') {
|
||||
object['username'] = object['username'].toLowerCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DatabaseController {
|
||||
adapter: StorageAdapter;
|
||||
schemaCache: any;
|
||||
@@ -570,6 +589,8 @@ class DatabaseController {
|
||||
}
|
||||
}
|
||||
update = transformObjectACL(update);
|
||||
convertEmailToLowercase(update, className, this.options);
|
||||
convertUsernameToLowercase(update, className, this.options);
|
||||
transformAuthData(className, update, schema);
|
||||
if (validateOnly) {
|
||||
return this.adapter.find(className, schema, query, {}).then(result => {
|
||||
@@ -819,6 +840,8 @@ class DatabaseController {
|
||||
const originalObject = object;
|
||||
object = transformObjectACL(object);
|
||||
|
||||
convertEmailToLowercase(object, className, this.options);
|
||||
convertUsernameToLowercase(object, className, this.options);
|
||||
object.createdAt = { iso: object.createdAt, __type: 'Date' };
|
||||
object.updatedAt = { iso: object.updatedAt, __type: 'Date' };
|
||||
|
||||
@@ -1212,7 +1235,7 @@ class DatabaseController {
|
||||
keys,
|
||||
readPreference,
|
||||
hint,
|
||||
caseInsensitive,
|
||||
caseInsensitive: this.options.enableCollationCaseComparison ? false : caseInsensitive,
|
||||
explain,
|
||||
};
|
||||
Object.keys(sort).forEach(fieldName => {
|
||||
@@ -1716,25 +1739,27 @@ class DatabaseController {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
||||
.catch(error => {
|
||||
logger.warn('Unable to create case insensitive username index: ', error);
|
||||
throw error;
|
||||
});
|
||||
if (!this.options.enableCollationCaseComparison) {
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['username'], 'case_insensitive_username', true)
|
||||
.catch(error => {
|
||||
logger.warn('Unable to create case insensitive username index: ', error);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['email'], 'case_insensitive_email', true)
|
||||
.catch(error => {
|
||||
logger.warn('Unable to create case insensitive email index: ', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
await this.adapter.ensureUniqueness('_User', requiredUserFields, ['email']).catch(error => {
|
||||
logger.warn('Unable to ensure uniqueness for user email addresses: ', error);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await this.adapter
|
||||
.ensureIndex('_User', requiredUserFields, ['email'], 'case_insensitive_email', true)
|
||||
.catch(error => {
|
||||
logger.warn('Unable to create case insensitive email index: ', error);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await this.adapter.ensureUniqueness('_Role', requiredRoleFields, ['name']).catch(error => {
|
||||
logger.warn('Unable to ensure uniqueness for role name: ', error);
|
||||
throw error;
|
||||
@@ -1817,7 +1842,7 @@ class DatabaseController {
|
||||
keyUpdate &&
|
||||
typeof keyUpdate === 'object' &&
|
||||
keyUpdate.__op &&
|
||||
['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1
|
||||
['Add', 'AddUnique', 'Remove', 'Increment', 'SetOnInsert'].indexOf(keyUpdate.__op) > -1
|
||||
) {
|
||||
// only valid ops that produce an actionable result
|
||||
// the op may have happened on a keypath
|
||||
|
||||
@@ -139,6 +139,20 @@ module.exports.ParseServerOptions = {
|
||||
help: 'A collection prefix for the classes',
|
||||
default: '',
|
||||
},
|
||||
convertEmailToLowercase: {
|
||||
env: 'PARSE_SERVER_CONVERT_EMAIL_TO_LOWERCASE',
|
||||
help:
|
||||
'Optional. If set to `true`, the `email` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `email` property is stored as set, without any case modifications. Default is `false`.',
|
||||
action: parsers.booleanParser,
|
||||
default: false,
|
||||
},
|
||||
convertUsernameToLowercase: {
|
||||
env: 'PARSE_SERVER_CONVERT_USERNAME_TO_LOWERCASE',
|
||||
help:
|
||||
'Optional. If set to `true`, the `username` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `username` property is stored as set, without any case modifications. Default is `false`.',
|
||||
action: parsers.booleanParser,
|
||||
default: false,
|
||||
},
|
||||
customPages: {
|
||||
env: 'PARSE_SERVER_CUSTOM_PAGES',
|
||||
help: 'custom pages for password validation and reset',
|
||||
@@ -203,6 +217,13 @@ module.exports.ParseServerOptions = {
|
||||
action: parsers.booleanParser,
|
||||
default: true,
|
||||
},
|
||||
enableCollationCaseComparison: {
|
||||
env: 'PARSE_SERVER_ENABLE_COLLATION_CASE_COMPARISON',
|
||||
help:
|
||||
'Optional. If set to `true`, the collation rule of case comparison for queries and indexes is enabled. Enable this option to run Parse Server with MongoDB Atlas Serverless or AWS Amazon DocumentDB. If `false`, the collation rule of case comparison is disabled. Default is `false`.',
|
||||
action: parsers.booleanParser,
|
||||
default: false,
|
||||
},
|
||||
enableExpressErrorHandler: {
|
||||
env: 'PARSE_SERVER_ENABLE_EXPRESS_ERROR_HANDLER',
|
||||
help: 'Enables the default express error handler for all errors',
|
||||
@@ -317,13 +338,13 @@ module.exports.ParseServerOptions = {
|
||||
maintenanceKey: {
|
||||
env: 'PARSE_SERVER_MAINTENANCE_KEY',
|
||||
help:
|
||||
'(Optional) The maintenance key is used for modifying internal fields of Parse Server.<br><br>\u26A0\uFE0F This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.',
|
||||
'(Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.<br><br>\u26A0\uFE0F This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.',
|
||||
required: true,
|
||||
},
|
||||
maintenanceKeyIps: {
|
||||
env: 'PARSE_SERVER_MAINTENANCE_KEY_IPS',
|
||||
help:
|
||||
"(Optional) Restricts the use of maintenance key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `maintenanceKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the maintenance key can be used from any IP address.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the maintenance key.",
|
||||
"(Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `\"0.0.0.0/0,::0\"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key.",
|
||||
action: parsers.arrayParser,
|
||||
default: ['127.0.0.1', '::1'],
|
||||
},
|
||||
@@ -335,7 +356,7 @@ module.exports.ParseServerOptions = {
|
||||
masterKeyIps: {
|
||||
env: 'PARSE_SERVER_MASTER_KEY_IPS',
|
||||
help:
|
||||
"(Optional) Restricts the use of master key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `masterKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.<br><br>To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the master key.",
|
||||
"(Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `\"0.0.0.0/0,::0\"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the master key.",
|
||||
action: parsers.arrayParser,
|
||||
default: ['127.0.0.1', '::1'],
|
||||
},
|
||||
|
||||
+6
-3
@@ -28,6 +28,8 @@
|
||||
* @property {String} cloud Full path to your cloud code main.js
|
||||
* @property {Number|Boolean} cluster Run with cluster, optionally set the number of processes default to os.cpus().length
|
||||
* @property {String} collectionPrefix A collection prefix for the classes
|
||||
* @property {Boolean} convertEmailToLowercase Optional. If set to `true`, the `email` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `email` property is stored as set, without any case modifications. Default is `false`.
|
||||
* @property {Boolean} convertUsernameToLowercase Optional. If set to `true`, the `username` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `username` property is stored as set, without any case modifications. Default is `false`.
|
||||
* @property {CustomPagesOptions} customPages custom pages for password validation and reset
|
||||
* @property {Adapter<StorageAdapter>} databaseAdapter Adapter module for the database; any options that are not explicitly described here are passed directly to the database client.
|
||||
* @property {DatabaseOptions} databaseOptions Options to pass to the database client
|
||||
@@ -39,6 +41,7 @@
|
||||
* @property {Boolean} emailVerifyTokenReuseIfValid Set to `true` if a email verification token should be reused in case another token is requested but there is a token that is still valid, i.e. has not expired. This avoids the often observed issue that a user requests multiple emails and does not know which link contains a valid token because each newly generated token would invalidate the previous token.<br><br>Default is `false`.<br>Requires option `verifyUserEmails: true`.
|
||||
* @property {Number} emailVerifyTokenValidityDuration Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to `undefined`, then the token never expires.<br><br>For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).<br><br>Default is `undefined`.<br>Requires option `verifyUserEmails: true`.
|
||||
* @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true
|
||||
* @property {Boolean} enableCollationCaseComparison Optional. If set to `true`, the collation rule of case comparison for queries and indexes is enabled. Enable this option to run Parse Server with MongoDB Atlas Serverless or AWS Amazon DocumentDB. If `false`, the collation rule of case comparison is disabled. Default is `false`.
|
||||
* @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors
|
||||
* @property {Boolean} encodeParseObjectInCloudFunction If set to `true`, a `Parse.Object` that is in the payload when calling a Cloud Function will be converted to an instance of `Parse.Object`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Object` but is not an actual instance of `Parse.Object`. Default is `false`. <br><br>ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Object`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where JavaScript objects are not converted to actual instances of `Parse.Object`.
|
||||
* @property {String} encryptionKey Key for encrypting your files
|
||||
@@ -60,10 +63,10 @@
|
||||
* @property {String} logLevel Sets the level for logs
|
||||
* @property {LogLevels} logLevels (Optional) Overrides the log levels used internally by Parse Server to log events.
|
||||
* @property {String} logsFolder Folder for the logs (defaults to './logs'); set to null to disable file based logging
|
||||
* @property {String} maintenanceKey (Optional) The maintenance key is used for modifying internal fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.
|
||||
* @property {String[]} maintenanceKeyIps (Optional) Restricts the use of maintenance key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `maintenanceKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the maintenance key can be used from any IP address.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the maintenance key.
|
||||
* @property {String} maintenanceKey (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.
|
||||
* @property {String[]} maintenanceKeyIps (Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key.
|
||||
* @property {String} masterKey Your Parse Master Key
|
||||
* @property {String[]} masterKeyIps (Optional) Restricts the use of master key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `masterKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.<br><br>To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the master key.
|
||||
* @property {String[]} masterKeyIps (Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the master key.
|
||||
* @property {Number} maxLimit Max value for limit option on queries, defaults to unlimited
|
||||
* @property {Number|String} maxLogFiles Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null)
|
||||
* @property {String} maxUploadSize Max file size for uploads, defaults to 20mb
|
||||
|
||||
+13
-4
@@ -47,16 +47,16 @@ export interface ParseServerOptions {
|
||||
appId: string;
|
||||
/* Your Parse Master Key */
|
||||
masterKey: string;
|
||||
/* (Optional) The maintenance key is used for modifying internal fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */
|
||||
/* (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */
|
||||
maintenanceKey: string;
|
||||
/* URL to your parse server with http:// or https://.
|
||||
:ENV: PARSE_SERVER_URL */
|
||||
serverURL: string;
|
||||
/* (Optional) Restricts the use of master key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `masterKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.<br><br>To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the master key.
|
||||
/* (Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the master key.
|
||||
:DEFAULT: ["127.0.0.1","::1"] */
|
||||
masterKeyIps: ?(string[]);
|
||||
/* (Optional) Restricts the use of maintenance key permissions to a list of IP addresses.<br><br>This option accepts a list of single IP addresses, for example:<br>`['10.0.0.1', '10.0.0.2']`<br><br>You can also use CIDR notation to specify an IP address range, for example:<br>`['10.0.1.0/24']`<br><br>Special cases:<br>- Setting an empty array `[]` means that `maintenanceKey` cannot be used even in Parse Server Cloud Code.<br>- Setting `['0.0.0.0/0']` means disabling the filter and the maintenance key can be used from any IP address.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server itself, is allowed to use the maintenance key.
|
||||
:DEFAULT: ["127.0.0.1","::1"] */
|
||||
/* (Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.<br><br>This option accepts a list of single IP addresses, for example `['10.0.0.1', '10.0.0.2']`. You can also use CIDR notation to specify an IP address range, for example `['10.0.1.0/24']`.<br><br><b>Special scenarios:</b><br>- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.<br>- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.<br><br><b>Considerations:</b><br>- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.<br>- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.<br>- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.<br>- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.<br><br>Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key.
|
||||
:DEFAULT: ["127.0.0.1","::1"] */
|
||||
maintenanceKeyIps: ?(string[]);
|
||||
/* Sets the app name */
|
||||
appName: ?string;
|
||||
@@ -103,6 +103,15 @@ export interface ParseServerOptions {
|
||||
databaseOptions: ?DatabaseOptions;
|
||||
/* Adapter module for the database; any options that are not explicitly described here are passed directly to the database client. */
|
||||
databaseAdapter: ?Adapter<StorageAdapter>;
|
||||
/* Optional. If set to `true`, the collation rule of case comparison for queries and indexes is enabled. Enable this option to run Parse Server with MongoDB Atlas Serverless or AWS Amazon DocumentDB. If `false`, the collation rule of case comparison is disabled. Default is `false`.
|
||||
:DEFAULT: false */
|
||||
enableCollationCaseComparison: ?boolean;
|
||||
/* Optional. If set to `true`, the `email` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `email` property is stored as set, without any case modifications. Default is `false`.
|
||||
:DEFAULT: false */
|
||||
convertEmailToLowercase: ?boolean;
|
||||
/* Optional. If set to `true`, the `username` property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If `false`, the `username` property is stored as set, without any case modifications. Default is `false`.
|
||||
:DEFAULT: false */
|
||||
convertUsernameToLowercase: ?boolean;
|
||||
/* Full path to your cloud code main.js */
|
||||
cloud: ?string;
|
||||
/* A collection prefix for the classes
|
||||
|
||||
@@ -75,6 +75,8 @@ class ParseServer {
|
||||
const allControllers = controllers.getControllers(options);
|
||||
options.state = 'initialized';
|
||||
this.config = Config.put(Object.assign({}, options, allControllers));
|
||||
this.config.masterKeyIpsStore = new Map();
|
||||
this.config.maintenanceKeyIpsStore = new Map();
|
||||
logging.setLogger(allControllers.loggerController);
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -478,6 +478,7 @@ _UnsafeRestQuery.prototype.replaceInQuery = async function () {
|
||||
className: inQueryValue.className,
|
||||
restWhere: inQueryValue.where,
|
||||
restOptions: additionalOptions,
|
||||
context: this.context,
|
||||
});
|
||||
return subquery.execute().then(response => {
|
||||
transformInQuery(inQueryObject, subquery.className, response.results);
|
||||
@@ -537,6 +538,7 @@ _UnsafeRestQuery.prototype.replaceNotInQuery = async function () {
|
||||
className: notInQueryValue.className,
|
||||
restWhere: notInQueryValue.where,
|
||||
restOptions: additionalOptions,
|
||||
context: this.context,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
@@ -609,6 +611,7 @@ _UnsafeRestQuery.prototype.replaceSelect = async function () {
|
||||
className: selectValue.query.className,
|
||||
restWhere: selectValue.query.where,
|
||||
restOptions: additionalOptions,
|
||||
context: this.context,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
@@ -671,6 +674,7 @@ _UnsafeRestQuery.prototype.replaceDontSelect = async function () {
|
||||
className: dontSelectValue.query.className,
|
||||
restWhere: dontSelectValue.query.where,
|
||||
restOptions: additionalOptions,
|
||||
context: this.context,
|
||||
});
|
||||
|
||||
return subquery.execute().then(response => {
|
||||
@@ -860,6 +864,7 @@ _UnsafeRestQuery.prototype.handleInclude = function () {
|
||||
this.auth,
|
||||
this.response,
|
||||
this.include[0],
|
||||
this.context,
|
||||
this.restOptions
|
||||
);
|
||||
if (pathResponse.then) {
|
||||
@@ -946,7 +951,7 @@ _UnsafeRestQuery.prototype.handleAuthAdapters = async function () {
|
||||
// Adds included values to the response.
|
||||
// Path is a list of field names.
|
||||
// Returns a promise for an augmented response.
|
||||
function includePath(config, auth, response, path, restOptions = {}) {
|
||||
function includePath(config, auth, response, path, context, restOptions = {}) {
|
||||
var pointers = findPointers(response.results, path);
|
||||
if (pointers.length == 0) {
|
||||
return response;
|
||||
@@ -1026,6 +1031,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
className,
|
||||
restWhere: where,
|
||||
restOptions: includeRestOptions,
|
||||
context: context,
|
||||
});
|
||||
return query.execute({ op: 'get' }).then(results => {
|
||||
results.className = className;
|
||||
|
||||
+31
-2
@@ -368,9 +368,36 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
|
||||
};
|
||||
|
||||
// Add default fields
|
||||
this.data.updatedAt = this.updatedAt;
|
||||
if (!this.query) {
|
||||
this.data.createdAt = this.updatedAt;
|
||||
// allow customizing createdAt and updatedAt when using maintenance key
|
||||
if (
|
||||
this.auth.isMaintenance &&
|
||||
this.data.createdAt &&
|
||||
this.data.createdAt.__type === 'Date'
|
||||
) {
|
||||
this.data.createdAt = this.data.createdAt.iso;
|
||||
|
||||
if (this.data.updatedAt && this.data.updatedAt.__type === 'Date') {
|
||||
const createdAt = new Date(this.data.createdAt);
|
||||
const updatedAt = new Date(this.data.updatedAt.iso);
|
||||
|
||||
if (updatedAt < createdAt) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
'updatedAt cannot occur before createdAt'
|
||||
);
|
||||
}
|
||||
|
||||
this.data.updatedAt = this.data.updatedAt.iso;
|
||||
}
|
||||
// if no updatedAt is provided, set it to createdAt to match default behavior
|
||||
else {
|
||||
this.data.updatedAt = this.data.createdAt;
|
||||
}
|
||||
} else {
|
||||
this.data.updatedAt = this.updatedAt;
|
||||
this.data.createdAt = this.updatedAt;
|
||||
}
|
||||
|
||||
// Only assign new objectId if we are creating new object
|
||||
if (!this.data.objectId) {
|
||||
@@ -382,6 +409,8 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
|
||||
});
|
||||
}
|
||||
} else if (schema) {
|
||||
this.data.updatedAt = this.updatedAt;
|
||||
|
||||
Object.keys(this.data).forEach(fieldName => {
|
||||
setRequiredFieldIfNeeded(fieldName, false);
|
||||
});
|
||||
|
||||
@@ -106,6 +106,13 @@ export class ClassesRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
handleCreate(req) {
|
||||
if (
|
||||
this.className(req) === '_User' &&
|
||||
typeof req.body?.objectId === 'string' &&
|
||||
req.body.objectId.startsWith('role:')
|
||||
) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
||||
}
|
||||
return rest.create(
|
||||
req.config,
|
||||
req.auth,
|
||||
|
||||
@@ -159,9 +159,9 @@ export class FilesRouter {
|
||||
} else if (contentType && contentType.includes('/')) {
|
||||
extension = contentType.split('/')[1];
|
||||
}
|
||||
extension = extension.split(' ').join('');
|
||||
extension = extension?.split(' ')?.join('');
|
||||
|
||||
if (!isValidExtension(extension)) {
|
||||
if (extension && !isValidExtension(extension)) {
|
||||
next(
|
||||
new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { logger } from '../logger';
|
||||
function parseObject(obj, config) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => {
|
||||
return parseObject(item);
|
||||
return parseObject(item, config);
|
||||
});
|
||||
} else if (obj && obj.__type == 'Date') {
|
||||
return Object.assign(new Date(obj.iso), obj);
|
||||
|
||||
+44
-3
@@ -10,9 +10,9 @@ import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageA
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { RateLimitOptions } from './Options/Definitions';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import ipRangeCheck from 'ip-range-check';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import { createClient } from 'redis';
|
||||
import { BlockList, isIPv4 } from 'net';
|
||||
|
||||
export const DEFAULT_ALLOWED_HEADERS =
|
||||
'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control';
|
||||
@@ -23,6 +23,46 @@ const getMountForRequest = function (req) {
|
||||
return req.protocol + '://' + req.get('host') + mountPath;
|
||||
};
|
||||
|
||||
const getBlockList = (ipRangeList, store) => {
|
||||
if (store.get('blockList')) return store.get('blockList');
|
||||
const blockList = new BlockList();
|
||||
ipRangeList.forEach(fullIp => {
|
||||
if (fullIp === '::/0' || fullIp === '::') {
|
||||
store.set('allowAllIpv6', true);
|
||||
return;
|
||||
}
|
||||
if (fullIp === '0.0.0.0/0' || fullIp === '0.0.0.0') {
|
||||
store.set('allowAllIpv4', true);
|
||||
return;
|
||||
}
|
||||
const [ip, mask] = fullIp.split('/');
|
||||
if (!mask) {
|
||||
blockList.addAddress(ip, isIPv4(ip) ? 'ipv4' : 'ipv6');
|
||||
} else {
|
||||
blockList.addSubnet(ip, Number(mask), isIPv4(ip) ? 'ipv4' : 'ipv6');
|
||||
}
|
||||
});
|
||||
store.set('blockList', blockList);
|
||||
return blockList;
|
||||
};
|
||||
|
||||
export const checkIp = (ip, ipRangeList, store) => {
|
||||
const incomingIpIsV4 = isIPv4(ip);
|
||||
const blockList = getBlockList(ipRangeList, store);
|
||||
|
||||
if (store.get(ip)) return true;
|
||||
if (store.get('allowAllIpv4') && incomingIpIsV4) return true;
|
||||
if (store.get('allowAllIpv6') && !incomingIpIsV4) return true;
|
||||
const result = blockList.check(ip, incomingIpIsV4 ? 'ipv4' : 'ipv6');
|
||||
|
||||
// If the ip is in the list, we store the result in the store
|
||||
// so we have a optimized path for the next request
|
||||
if (ipRangeList.includes(ip) && result) {
|
||||
store.set(ip, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Checks that the request is authorized for this app and checks user
|
||||
// auth too.
|
||||
// The bodyparser should run before this middleware.
|
||||
@@ -183,7 +223,7 @@ export function handleParseHeaders(req, res, next) {
|
||||
const isMaintenance =
|
||||
req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey;
|
||||
if (isMaintenance) {
|
||||
if (ipRangeCheck(clientIp, req.config.maintenanceKeyIps || [])) {
|
||||
if (checkIp(clientIp, req.config.maintenanceKeyIps || [], req.config.maintenanceKeyIpsStore)) {
|
||||
req.auth = new auth.Auth({
|
||||
config: req.config,
|
||||
installationId: info.installationId,
|
||||
@@ -199,7 +239,8 @@ export function handleParseHeaders(req, res, next) {
|
||||
}
|
||||
|
||||
let isMaster = info.masterKey === req.config.masterKey;
|
||||
if (isMaster && !ipRangeCheck(clientIp, req.config.masterKeyIps || [])) {
|
||||
|
||||
if (isMaster && !checkIp(clientIp, req.config.masterKeyIps || [], req.config.masterKeyIpsStore)) {
|
||||
const log = req.config?.loggerController || defaultLogger;
|
||||
log.error(
|
||||
`Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.`
|
||||
|
||||
+7
-1
@@ -86,6 +86,12 @@ const Category = {
|
||||
};
|
||||
|
||||
function getStore(category, name, applicationId) {
|
||||
const invalidNameRegex = /['"`]/;
|
||||
if (invalidNameRegex.test(name)) {
|
||||
// Prevent a malicious user from injecting properties into the store
|
||||
return {};
|
||||
}
|
||||
|
||||
const path = name.split('.');
|
||||
path.splice(-1); // remove last component
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
@@ -94,7 +100,7 @@ function getStore(category, name, applicationId) {
|
||||
for (const component of path) {
|
||||
store = store[component];
|
||||
if (!store) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return store;
|
||||
|
||||
Reference in New Issue
Block a user