Compare commits

..
31 Commits
Author SHA1 Message Date
semantic-release-bot 9dc0235b5d chore(release): 6.5.5 [skip ci]
## [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))
2024-03-19 16:43:32 +00:00
Manuel 5ae6d6a36d fix: 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) 2024-03-19 17:42:24 +01:00
semantic-release-bot 3773203a37 chore(release): 6.5.4 [skip ci]
## [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))
2024-03-16 16:20:47 +00:00
Jayson Ng 8ff444d42e fix: Server crashes when receiving an array of Parse.Pointer in the request body (#9012) 2024-03-16 17:19:54 +01:00
semantic-release-bot 9cb44c08cc chore(release): 6.5.3 [skip ci]
## [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))
2024-03-16 13:41:15 +00:00
Manuel 09b6a95264 ci: Fix auto-release (#9021) 2024-03-16 14:40:12 +01:00
Parse Platform 422958e246 fix: Security upgrade follow-redirects from 1.15.5 to 1.15.6 (#9019) 2024-03-16 10:10:50 +01:00
Manuel b8535b3db9 ci: Fix LTS releases are published as pre-releases (#8989) 2024-03-05 20:00:09 +01:00
Corey 9282bc595c ci: Fix failing Docker release by removing arm/v6 and arm/v7 support (#8977) 2024-03-05 08:56:40 +01:00
Parse Platform 47184f0734 refactor: Upgrade graphql-list-fields from 2.0.2 to 2.0.4 (#8973) 2024-03-03 00:46:54 +01:00
Parse Platform d53c1f3668 refactor: Upgrade winston-daily-rotate-file from 4.7.1 to 5.0.0 (#8974) 2024-03-03 00:03:05 +01:00
semantic-release-bot d3ec2e2be7 chore(release): 6.5.2 [skip ci]
## [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))
2024-03-02 21:48:11 +00:00
Parse Platform 0fa0aabefe fix: Security upgrade @parse/push-adapter from 5.1.0 to 5.1.1 (#8975) 2024-03-02 22:47:16 +01:00
semantic-release-bot 46761d3ae2 chore(release): 6.5.1 [skip ci]
## [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))
2024-03-02 20:43:02 +00:00
Parse Platform bba24dd827 fix: Security upgrade @parse/push-adapter from 5.0.2 to 5.1.0 (#8972) 2024-03-02 21:42:04 +01:00
Manuel 30258be121 docs: Remove incorrect change log entries (#8963) 2024-03-01 17:14:29 +01:00
semantic-release-bot 5f9a27fb8e chore(release): 6.5.0 [skip ci]
# [6.5.0](https://github.com/parse-community/parse-server/compare/6.4.0...6.5.0) (2024-03-01)

### 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))
* 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))
* 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))

### 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))
2024-03-01 16:04:36 +00:00
Manuel 297faaece4 ci: Fix incorrect release branch config (#8962) 2024-03-01 17:03:43 +01:00
Manuel a6e6549435 fix: 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) 2024-03-01 16:51:02 +01:00
Parse Platform 244e3431cf refactor: Upgrade redis from 4.6.12 to 4.6.13 (#8955) 2024-02-27 14:42:47 +01:00
Parse Platform 33c648dc81 refactor: Upgrade uuid from 9.0.0 to 9.0.1 (#8943) 2024-02-26 01:22:37 +01:00
Parse Platform 4524c35d51 refactor: Upgrade follow-redirects from 1.15.2 to 1.15.5 (#8931) 2024-02-24 17:30:05 +01:00
Parse Platform 70e0cb3744 refactor: Upgrade jwks-rsa from 2.1.5 to 3.1.0 (#8932) 2024-02-24 12:22:00 +01:00
Parse Platform 519dee9b89 refactor: Upgrade winston from 3.8.2 to 3.11.0 (#8933) 2024-02-23 18:18:24 +01:00
Parse Platform 897acb76a5 refactor: Upgrade semver from 7.5.2 to 7.5.4 (#8934) 2024-02-22 13:05:39 +01:00
Parse Platform e5de9daa18 refactor: Upgrade @parse/fs-files-adapter from 1.2.2 to 2.0.1 (#8930) 2024-02-20 18:17:11 +01:00
Parse Platform 223fde0f31 refactor: Upgrade pg-promise from 11.5.0 to 11.5.4 (#8924) 2024-02-17 00:41:11 +01:00
Parse Platform d0a5af33ca refactor: Upgrade otpauth from 9.1.2 to 9.2.2 (#8923) 2024-02-17 00:10:26 +01:00
Parse Platform 8fe0ae7a2c refactor: Upgrade ws from 8.13.0 to 8.16.0 (#8921) 2024-02-15 23:54:05 +01:00
Parse Platform 5179501885 refactor: Upgrade redis from 4.6.6 to 4.6.12 (#8922) 2024-02-15 19:35:22 +01:00
Parse Platform 933e8226df refactor: Upgrade lru-cache from 9.1.1 to 10.1.0 (#8870) 2024-01-08 12:15:33 +01:00
50 changed files with 5240 additions and 1936 deletions
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout default branch
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 14
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout default branch
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Compose branch name for PR
id: branch
run: echo "::set-output name=name::ci-bump-environment"
+113 -103
View File
@@ -1,14 +1,14 @@
name: ci
on:
push:
branches: [release, alpha, beta, next-major, 'release-[0-9]+.x.x']
branches: [ release, alpha, beta, next-major, 'release-[0-9]+.x.x' ]
pull_request:
branches:
- '**'
paths-ignore:
- '**/**.md'
env:
NODE_VERSION: 20.11.1
NODE_VERSION: 19.3.0
PARSE_SERVER_TEST_TIMEOUT: 20000
jobs:
check-code-analysis:
@@ -21,29 +21,29 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ['javascript']
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
source-root: src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
source-root: src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
check-ci:
name: Node Engine Check
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
@@ -54,37 +54,37 @@ jobs:
- name: CI Node Engine Check
run: npm run ci:checkNodeEngine
check-lint:
name: Lint
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- run: npm run lint
name: Lint
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- run: npm run lint
check-definitions:
name: Check Definitions
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
name: Check Definitions
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
@@ -95,32 +95,32 @@ jobs:
- name: CI Definitions Check
run: npm run ci:definitionsCheck
check-circular:
name: Circular Dependencies
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- run: npm run madge:circular
name: Circular Dependencies
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- run: npm run madge:circular
check-docker:
name: Docker Build
timeout-minutes: 15
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v2
@@ -136,7 +136,7 @@ jobs:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Check NPM lock file version
uses: mansona/npm-lockfile-version@v1
with:
@@ -147,33 +147,37 @@ jobs:
include:
- name: MongoDB 4.2, ReplicaSet
MONGODB_VERSION: 4.2.19
MONGODB_TOPOLOGY: replset
NODE_VERSION: 20.11.1
MONGODB_TOPOLOGY: replicaset
NODE_VERSION: 19.3.0
- name: MongoDB 4.4, ReplicaSet
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: replset
NODE_VERSION: 20.11.1
MONGODB_TOPOLOGY: replicaset
NODE_VERSION: 19.3.0
- name: MongoDB 5, ReplicaSet
MONGODB_VERSION: 5.3.2
MONGODB_TOPOLOGY: replset
NODE_VERSION: 20.11.1
MONGODB_TOPOLOGY: replicaset
NODE_VERSION: 19.3.0
- name: MongoDB 6, ReplicaSet
MONGODB_VERSION: 6.0.2
MONGODB_TOPOLOGY: replset
NODE_VERSION: 20.11.1
- name: MongoDB 7, ReplicaSet
MONGODB_VERSION: 7.0.1
MONGODB_TOPOLOGY: replset
NODE_VERSION: 20.11.1
MONGODB_TOPOLOGY: replicaset
NODE_VERSION: 19.3.0
- name: Redis Cache
PARSE_SERVER_TEST_CACHE: redis
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 20.11.1
NODE_VERSION: 19.3.0
- name: Node 14
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 14.21.1
- name: Node 16
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 16.18.1
- name: Node 18
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 18.19.1
NODE_VERSION: 18.12.1
fail-fast: false
name: ${{ matrix.name }}
timeout-minutes: 15
@@ -182,7 +186,7 @@ jobs:
redis:
image: redis
ports:
- 6379:6379
- 6379:6379
env:
MONGODB_VERSION: ${{ matrix.MONGODB_VERSION }}
MONGODB_TOPOLOGY: ${{ matrix.MONGODB_TOPOLOGY }}
@@ -192,13 +196,16 @@ jobs:
steps:
- name: Fix usage of insecure GitHub protocol
run: sudo git config --system url."https://github".insteadOf "git://github"
- uses: actions/checkout@v4
- name: Fix git protocol for Node 14
if: ${{ startsWith(matrix.NODE_VERSION, '14.') }}
run: sudo git config --system url."https://github".insteadOf "ssh://git@github"
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.NODE_VERSION }}
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
@@ -215,27 +222,30 @@ jobs:
strategy:
matrix:
include:
- name: PostgreSQL 13, PostGIS 3.1
POSTGRES_IMAGE: postgis/postgis:13-3.1
NODE_VERSION: 20.11.1
- name: PostgreSQL 13, PostGIS 3.2
POSTGRES_IMAGE: postgis/postgis:13-3.2
NODE_VERSION: 20.11.1
- name: PostgreSQL 11, PostGIS 3.0
POSTGRES_IMAGE: postgis/postgis:11-3.0
NODE_VERSION: 19.3.0
- name: PostgreSQL 11, PostGIS 3.1
POSTGRES_IMAGE: postgis/postgis:11-3.1
NODE_VERSION: 19.3.0
- name: PostgreSQL 11, PostGIS 3.2
POSTGRES_IMAGE: postgis/postgis:11-3.2
NODE_VERSION: 19.3.0
- name: PostgreSQL 11, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:11-3.3
NODE_VERSION: 19.3.0
- name: PostgreSQL 12, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:12-3.3
NODE_VERSION: 19.3.0
- name: PostgreSQL 13, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:13-3.3
NODE_VERSION: 20.11.1
- name: PostgreSQL 13, PostGIS 3.4
POSTGRES_IMAGE: postgis/postgis:13-3.4
NODE_VERSION: 20.11.1
- name: PostgreSQL 14, PostGIS 3.4
POSTGRES_IMAGE: postgis/postgis:14-3.4
NODE_VERSION: 20.11.1
- name: PostgreSQL 15, PostGIS 3.4
POSTGRES_IMAGE: postgis/postgis:15-3.4
NODE_VERSION: 20.11.1
- name: PostgreSQL 16, PostGIS 3.4
POSTGRES_IMAGE: postgis/postgis:15-3.4
NODE_VERSION: 20.11.1
NODE_VERSION: 19.3.0
- name: PostgreSQL 14, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:14-3.3
NODE_VERSION: 19.3.0
- name: PostgreSQL 15, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:15-3.3
NODE_VERSION: 19.3.0
fail-fast: false
name: ${{ matrix.name }}
timeout-minutes: 15
@@ -261,13 +271,13 @@ jobs:
PARSE_SERVER_TEST_DATABASE_URI: postgres://postgres:postgres@localhost:5432/parse_server_postgres_adapter_test_database
NODE_VERSION: ${{ matrix.NODE_VERSION }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.NODE_VERSION }}
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
+11 -11
View File
@@ -12,15 +12,15 @@ jobs:
- name: Determine trigger branch name
id: branch
run: echo "::set-output name=trigger_branch::${GITHUB_REF#refs/*/}"
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
persist-credentials: false
- uses: actions/setup-node@v4
- uses: actions/setup-node@v2
with:
node-version: 18.19.1
node-version: 18.1.0
registry-url: https://registry.npmjs.org/
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -42,7 +42,7 @@ jobs:
env:
REGISTRY: docker.io
IMAGE_NAME: parseplatform/parse-server
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
@@ -51,7 +51,7 @@ jobs:
id: branch
run: echo "::set-output name=branch_name::${GITHUB_REF#refs/*/}"
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
ref: ${{ needs.release.outputs.current_tag }}
- name: Set up QEMU
@@ -78,7 +78,7 @@ jobs:
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 }}
@@ -89,13 +89,13 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v1
with:
node-version: 18.19.1
node-version: 18.1.0
- name: Cache Node.js modules
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+3 -3
View File
@@ -14,7 +14,7 @@ env:
IMAGE_NAME: parseplatform/parse-server
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
@@ -23,7 +23,7 @@ jobs:
id: branch
run: echo "::set-output name=branch_name::${GITHUB_REF#refs/*/}"
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.ref }}
- name: Set up QEMU
@@ -51,7 +51,7 @@ jobs:
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 }}
+1 -1
View File
@@ -10,7 +10,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
| DEPPS4 | Remove convenience method for http request `Parse.Cloud.httpRequest` | [#7589](https://github.com/parse-community/parse-server/pull/7589) | 5.0.0 (2022) | 6.0.0 (2023) | removed | - |
| DEPPS5 | Config option `allowClientClassCreation` defaults to `false` | [#7925](https://github.com/parse-community/parse-server/pull/7925) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
| DEPPS6 | Auth providers disabled by default | [#7953](https://github.com/parse-community/parse-server/pull/7953) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
| DEPPS7 | Remove file trigger syntax `Parse.Cloud.beforeSaveFile((request) => {})` | [#7966](https://github.com/parse-community/parse-server/pull/7966) | 5.3.0 (2022) | 7.0.0 (2024) | removed | - |
| DEPPS7 | Remove file trigger syntax `Parse.Cloud.beforeSaveFile((request) => {})` | [#7966](https://github.com/parse-community/parse-server/pull/7966) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
| DEPPS8 | Login with expired 3rd party authentication token defaults to `false` | [#7079](https://github.com/parse-community/parse-server/pull/7079) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
| DEPPS9 | Rename LiveQuery `fields` option to `keys` | [#8389](https://github.com/parse-community/parse-server/issues/8389) | 6.0.0 (2023) | 7.0.0 (2024) | deprecated | - |
| DEPPS10 | Config option `encodeParseObjectInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 8.0.0 (2025) | deprecated | - |
+17 -15
View File
@@ -9,9 +9,9 @@
[![Coverage](https://img.shields.io/codecov/c/github/parse-community/parse-server/alpha.svg)](https://codecov.io/github/parse-community/parse-server?branch=alpha)
[![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases)
[![Node Version](https://img.shields.io/badge/nodejs-18,_20-green.svg?logo=node.js&style=flat)](https://nodejs.org)
[![Node Version](https://img.shields.io/badge/nodejs-14,_16,_18-green.svg?logo=node.js&style=flat)](https://nodejs.org)
[![MongoDB Version](https://img.shields.io/badge/mongodb-4.0,_4.2,_4.4,_5,_6-green.svg?logo=mongodb&style=flat)](https://www.mongodb.com)
[![Postgres Version](https://img.shields.io/badge/postgresql-13,_14,_15,_16-green.svg?logo=postgresql&style=flat)](https://www.postgresql.org)
[![Postgres Version](https://img.shields.io/badge/postgresql-11,_12,_13,_14,_15-green.svg?logo=postgresql&style=flat)](https://www.postgresql.org)
[![npm latest version](https://img.shields.io/npm/v/parse-server/latest.svg)](https://www.npmjs.com/package/parse-server)
[![npm beta version](https://img.shields.io/npm/v/parse-server/beta.svg)](https://www.npmjs.com/package/parse-server)
@@ -129,21 +129,22 @@ Parse Server is continuously tested with the most recent releases of Node.js to
| Version | Latest Version | End-of-Life | Compatible |
|------------|----------------|-------------|------------|
| Node.js 18 | 18.19.1 | April 2025 | ✅ Yes |
| Node.js 20 | 20.11.1 | April 2026 | ✅ Yes |
| Node.js 14 | 14.19.1 | April 2023 | ✅ Yes |
| Node.js 16 | 16.14.2 | April 2024 | ✅ Yes |
| Node.js 18 | 18.12.1 | April 2025 | ✅ Yes |
| Node.js 19 | 19.3.0 | June 2023 | ✅ Yes |
#### MongoDB
Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and [MongoDB lifecycle schedule](https://www.mongodb.com/support-policy/lifecycles) and only test against versions that are officially supported and have not reached their end-of-life date. We consider the end-of-life date of a MongoDB "rapid release" to be the same as its major version release.
| Version | Latest Version | End-of-Life | Compatible |
| ----------- | -------------- | ------------- | ---------- |
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
| MongoDB 7 | 7.0.1 | TDB | ✅ Yes |
|-------------|----------------|---------------|------------|
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
#### PostgreSQL
@@ -151,10 +152,11 @@ Parse Server is continuously tested with the most recent releases of PostgreSQL
| Version | PostGIS Version | End-of-Life | Parse Server Support | Compatible |
|-------------|--------------------|---------------|----------------------|------------|
| Postgres 13 | 3.1, 3.2, 3.3, 3.4 | November 2025 | <= 6.x (2023) | ✅ Yes |
| Postgres 14 | 3.4 | November 2026 | <= 7.x (2024) | ✅ Yes |
| Postgres 15 | 3.4 | November 2027 | <= 8.x (2025) | ✅ Yes |
| Postgres 16 | 3.4 | November 2028 | <= 9.x (2026) | ✅ Yes |
| Postgres 11 | 3.0, 3.1, 3.2, 3.3 | November 2023 | <= 5.x (2022) | ✅ Yes |
| Postgres 12 | 3.3 | November 2024 | <= 5.x (2022) | ✅ Yes |
| Postgres 13 | 3.3 | November 2025 | <= 6.x (2023) | ✅ Yes |
| Postgres 14 | 3.3 | November 2026 | <= 7.x (2024) | ✅ Yes |
| Postgres 15 | 3.3 | November 2027 | <= 8.x (2025) | ✅ Yes |
### Locally
-139
View File
@@ -1,142 +1,3 @@
# [7.0.0-alpha.15](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.14...7.0.0-alpha.15) (2024-02-14)
### Features
* Deprecation DEPPS7: Remove deprecated Cloud Code file trigger syntax ([#8855](https://github.com/parse-community/parse-server/issues/8855)) ([4e6a375](https://github.com/parse-community/parse-server/commit/4e6a375b5184ae0f7aa256a921eca4021c609435))
### BREAKING CHANGES
* Cloud Code file trigger syntax has been aligned with object trigger syntax, for example `Parse.Cloud.beforeDeleteFile'` has been changed to `Parse.Cloud.beforeDelete(Parse.File, (request) => {})'` ([4e6a375](4e6a375))
# [7.0.0-alpha.14](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.13...7.0.0-alpha.14) (2024-02-14)
### Bug Fixes
* GraphQL file upload fails in case of use of pointer or relation ([#8721](https://github.com/parse-community/parse-server/issues/8721)) ([1aba638](https://github.com/parse-community/parse-server/commit/1aba6382c873fb489d4a898d301e6da9fb6aa61b))
# [7.0.0-alpha.13](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.12...7.0.0-alpha.13) (2024-02-14)
### Bug Fixes
* Docker image not published to Docker Hub on new release ([#8905](https://github.com/parse-community/parse-server/issues/8905)) ([a2ac8d1](https://github.com/parse-community/parse-server/commit/a2ac8d133c71cd7b61e5ef59c4be915cfea85db6))
# [7.0.0-alpha.12](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.11...7.0.0-alpha.12) (2024-02-14)
### Features
* Add support for Node 20, drop support for Node 14, 16 ([#8907](https://github.com/parse-community/parse-server/issues/8907)) ([ced4872](https://github.com/parse-community/parse-server/commit/ced487246ea0ef72a8aa014991f003209b34841e))
### BREAKING CHANGES
* Removes support for Node 14 and 16 ([ced4872](ced4872))
# [7.0.0-alpha.11](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.10...7.0.0-alpha.11) (2024-01-22)
### Features
* Add support for Postgres 16 ([#8898](https://github.com/parse-community/parse-server/issues/8898)) ([99489b2](https://github.com/parse-community/parse-server/commit/99489b22e4f0982e6cb39992974b51aa8d3a31e4))
### BREAKING CHANGES
* Removes support for Postgres 11 and 12 ([99489b2](99489b2))
# [7.0.0-alpha.10](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.9...7.0.0-alpha.10) (2024-01-17)
### Features
* Add password validation via POST request for user with unverified email using master key and option `ignoreEmailVerification` ([#8895](https://github.com/parse-community/parse-server/issues/8895)) ([633a9d2](https://github.com/parse-community/parse-server/commit/633a9d25e4253e2125bc93c02ee8a37e0f5f7b83))
# [7.0.0-alpha.9](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.8...7.0.0-alpha.9) (2024-01-15)
### Bug Fixes
* Server crashes when receiving an array of `Parse.Pointer` in the request body ([#8784](https://github.com/parse-community/parse-server/issues/8784)) ([66e3603](https://github.com/parse-community/parse-server/commit/66e36039d8af654cfa0284666c0ddd94975dcb52))
# [7.0.0-alpha.8](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.7...7.0.0-alpha.8) (2024-01-15)
### Bug Fixes
* Incomplete user object in `verifyEmail` function if both username and email are changed ([#8889](https://github.com/parse-community/parse-server/issues/8889)) ([1eb95ae](https://github.com/parse-community/parse-server/commit/1eb95aeb41a96250e582d79a703f6adcb403c08b))
# [7.0.0-alpha.7](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.6...7.0.0-alpha.7) (2024-01-14)
### Bug Fixes
* Username is `undefined` in email verification link on email change ([#8887](https://github.com/parse-community/parse-server/issues/8887)) ([e315c13](https://github.com/parse-community/parse-server/commit/e315c137bf41bedfa8f0df537f2c3f6ab45b7e60))
# [7.0.0-alpha.6](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.5...7.0.0-alpha.6) (2024-01-14)
### Bug Fixes
* Parse Server option `emailVerifyTokenReuseIfValid: true` generates new token on every email verification request ([#8885](https://github.com/parse-community/parse-server/issues/8885)) ([0023ce4](https://github.com/parse-community/parse-server/commit/0023ce448a5e9423337d0e1a25648bde1156bc95))
# [7.0.0-alpha.5](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.4...7.0.0-alpha.5) (2024-01-06)
### Features
* Add `installationId`, `ip`, `resendRequest` to arguments passed to `verifyUserEmails` on verification email request ([#8873](https://github.com/parse-community/parse-server/issues/8873)) ([8adcbee](https://github.com/parse-community/parse-server/commit/8adcbee11283d3e95179ca2047e2615f52c18806))
### BREAKING CHANGES
* The `Parse.User` passed as argument if `verifyUserEmails` is set to a function is renamed from `user` to `object` for consistency with invocations of `verifyUserEmails` on signup or login; the user object is not a plain JavaScript object anymore but an instance of `Parse.User` ([8adcbee](8adcbee))
# [7.0.0-alpha.4](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.3...7.0.0-alpha.4) (2023-12-27)
### Features
* Add `Parse.User` as function parameter to Parse Server options `verifyUserEmails`, `preventLoginWithUnverifiedEmail` on login ([#8850](https://github.com/parse-community/parse-server/issues/8850)) ([972f630](https://github.com/parse-community/parse-server/commit/972f6300163b3cd7d95eeb95986e8322c95f821c))
# [7.0.0-alpha.3](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.2...7.0.0-alpha.3) (2023-12-26)
### Bug Fixes
* Conditional email verification not working in some cases if `verifyUserEmails`, `preventLoginWithUnverifiedEmail` set to functions ([#8838](https://github.com/parse-community/parse-server/issues/8838)) ([8e7a6b1](https://github.com/parse-community/parse-server/commit/8e7a6b1480c0117e6c73e7adc5a6619115a04e85))
### Features
* Allow `Parse.Session.current` on expired session token instead of throwing error ([#8722](https://github.com/parse-community/parse-server/issues/8722)) ([f9dde4a](https://github.com/parse-community/parse-server/commit/f9dde4a9f8a90c63f71172c9bc515b0f6c6d2e4a))
### BREAKING CHANGES
* `Parse.Session.current()` no longer throws an error if the session token is expired, but instead returns the session token with its expiration date to allow checking its validity ([f9dde4a](f9dde4a))
# [7.0.0-alpha.2](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.1...7.0.0-alpha.2) (2023-12-17)
### Features
* Add `installationId` to arguments for `verifyUserEmails`, `preventLoginWithUnverifiedEmail` ([#8836](https://github.com/parse-community/parse-server/issues/8836)) ([a22dbe1](https://github.com/parse-community/parse-server/commit/a22dbe16d5ac0090608f6caaf0ebd134925b7fd4))
# [7.0.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.5.0-alpha.2...7.0.0-alpha.1) (2023-12-10)
### Features
* Add support for MongoDB 7 ([#8761](https://github.com/parse-community/parse-server/issues/8761)) ([3de8494](https://github.com/parse-community/parse-server/commit/3de8494a221991dfd10a74e0a2dc89576265c9b7))
### BREAKING CHANGES
* `Parse.Query` no longer supports the BSON type `code`; although this feature was never officially documented, its removal is announced as a breaking change to protect deployments where it might be in use. ([3de8494](3de8494))
# [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)
+42
View File
@@ -1,3 +1,45 @@
## [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)
+4441 -965
View File
File diff suppressed because it is too large Load Diff
+22 -22
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "7.0.0-alpha.15",
"version": "6.5.5",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -24,8 +24,8 @@
"@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": "5.0.2",
"@parse/fs-files-adapter": "2.0.1",
"@parse/push-adapter": "5.1.1",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"commander": "10.0.1",
@@ -33,35 +33,35 @@
"deepcopy": "2.1.0",
"express": "4.18.2",
"express-rate-limit": "6.7.0",
"follow-redirects": "1.15.4",
"follow-redirects": "1.15.6",
"graphql": "16.8.1",
"graphql-list-fields": "2.0.2",
"graphql-list-fields": "2.0.4",
"graphql-relay": "0.10.0",
"graphql-tag": "2.12.6",
"intersect": "1.0.1",
"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": "5.9.0",
"mongodb": "4.10.0",
"mustache": "4.2.0",
"otpauth": "9.1.2",
"otpauth": "9.2.2",
"parse": "4.1.0",
"path-to-regexp": "6.2.1",
"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.11.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.16.0"
},
"devDependencies": {
"@actions/core": "1.9.1",
@@ -98,7 +98,7 @@
"madge": "5.0.1",
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
"mongodb-runner": "5.4.4",
"mongodb-runner": "4.8.1",
"mongodb-version-list": "1.0.0",
"node-abort-controller": "3.0.1",
"node-fetch": "3.2.10",
@@ -117,18 +117,18 @@
"lint-fix": "eslint --fix --cache ./",
"build": "babel src/ -d lib/ --copy-files",
"watch": "babel --watch src/ -d lib/ --copy-files",
"test:mongodb:runnerstart": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017",
"test:mongodb:runnerstart": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start",
"test:mongodb:testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine",
"test:mongodb": "npm run test:mongodb:runnerstart --dbversion=$npm_config_dbversion && npm run test:mongodb:testonly --dbversion=$npm_config_dbversion",
"test:mongodb:4.2.19": "npm run test:mongodb --dbversion=4.2.19",
"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:mongodb:7.0.1": "npm run test:mongodb --dbversion=7.0.1",
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017",
"posttest:mongodb": "mongodb-runner stop",
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.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-runner stop --all",
"posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner stop",
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 nyc jasmine",
"start": "node ./bin/parse-server",
"prettier": "prettier --write {src,spec}/{**/*,*}.js",
@@ -137,7 +137,7 @@
"madge:circular": "node_modules/.bin/madge ./src --circular"
},
"engines": {
"node": ">=18.0.0 <21"
"node": ">=14.21.0 <17 || >=18 <19"
},
"bin": {
"parse-server": "bin/parse-server"
+4 -14
View File
@@ -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
@@ -40,8 +40,8 @@ async function config() {
{ name: 'alpha', prerelease: true },
{ name: 'beta', prerelease: true },
'next-major',
// Long-Term-Support branch of previous major version
'release-6.x.x',
// Long-Term-Support branches
{ name: 'release-6.x.x', channel: '6.x.x' },
],
dryRun: false,
debug: true,
@@ -85,16 +85,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" },
]
}
],
],
};
-1
View File
@@ -28,7 +28,6 @@
"fit_exclude_node_version": true,
"it_exclude_dbs": true,
"describe_only_db": true,
"fdescribe_only_db": true,
"describe_only": true,
"on_db": true,
"defaultConfiguration": true,
+2 -6
View File
@@ -339,12 +339,11 @@ describe('AudiencesRouter', () => {
)
.then(result => {
expect(result).toBeTruthy();
database
.collection('test__Audience')
.find({ _id: audience.objectId })
.toArray()
.then(rows => {
.toArray((error, rows) => {
expect(error).toEqual(undefined);
expect(rows[0]['times_used']).toEqual(1);
expect(rows[0]['_last_used']).toEqual(now);
Parse._request(
@@ -363,9 +362,6 @@ describe('AudiencesRouter', () => {
.catch(error => {
done.fail(error);
});
})
.catch(error => {
done.fail(error);
});
});
});
+128 -45
View File
@@ -1454,6 +1454,7 @@ describe('oauth2 auth adapter', () => {
describe('apple signin auth adapter', () => {
const apple = require('../lib/Adapters/Auth/apple');
const jwt = require('jsonwebtoken');
const util = require('util');
const authUtils = require('../lib/Adapters/Auth/utils');
it('(using client id as string) should throw error with missing id_token', async () => {
@@ -1511,10 +1512,12 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
const result = await apple.validateAuthData(
{ id: 'the_user_id', token: 'the_token' },
@@ -1526,9 +1529,11 @@ describe('apple signin auth adapter', () => {
it('should not verify invalid id_token', async () => {
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
try {
await apple.validateAuthData(
@@ -1561,9 +1566,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData(
@@ -1581,9 +1588,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData(
@@ -1601,9 +1610,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData(
@@ -1619,9 +1630,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -1645,9 +1658,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -1672,9 +1687,11 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -1742,9 +1759,11 @@ describe('apple signin auth adapter', () => {
sub: 'a_different_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -2006,6 +2025,7 @@ describe('microsoft graph auth adapter', () => {
describe('facebook limited auth adapter', () => {
const facebook = require('../lib/Adapters/Auth/facebook');
const jwt = require('jsonwebtoken');
const util = require('util');
const authUtils = require('../lib/Adapters/Auth/utils');
// TODO: figure out a way to run this test alongside facebook classic tests
@@ -2066,11 +2086,18 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(),
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
const result = await facebook.validateAuthData(
{ id: 'the_user_id', token: 'the_token' },
@@ -2081,10 +2108,17 @@ describe('facebook limited auth adapter', () => {
});
it('should not verify invalid id_token', async () => {
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
try {
await facebook.validateAuthData(
@@ -2116,10 +2150,17 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(),
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData(
@@ -2136,10 +2177,17 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(),
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData(
@@ -2156,10 +2204,17 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(),
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData(
@@ -2174,10 +2229,17 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com',
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -2200,10 +2262,17 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com',
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -2227,10 +2296,17 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com',
sub: 'the_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
@@ -2306,10 +2382,17 @@ describe('facebook limited auth adapter', () => {
aud: 'invalid_client_id',
sub: 'a_different_user_id',
};
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
const fakeDecodedToken = {
header: { kid: '123', alg: 'RS256' },
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
const fakeGetSigningKeyAsyncFunction = () => {
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try {
+65 -16
View File
@@ -3635,7 +3635,7 @@ describe('afterLogin hook', () => {
});
describe('saveFile hooks', () => {
it('beforeSave(Parse.File) should return file that is already saved and not save anything to files adapter', async () => {
it('beforeSaveFile should return file that is already saved and not save anything to files adapter', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
Parse.Cloud.beforeSave(Parse.File, () => {
@@ -3651,7 +3651,7 @@ describe('saveFile hooks', () => {
expect(createFileSpy).not.toHaveBeenCalled();
});
it('beforeSave(Parse.File) should throw error', async () => {
it('beforeSaveFile should throw error', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeSave(Parse.File, () => {
throw new Parse.Error(400, 'some-error-message');
@@ -3664,7 +3664,7 @@ describe('saveFile hooks', () => {
}
});
it('beforeSave(Parse.File) should change values of uploaded file by editing fileObject directly', async () => {
it('beforeSaveFile should change values of uploaded file by editing fileObject directly', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
Parse.Cloud.beforeSave(Parse.File, async req => {
@@ -3693,7 +3693,7 @@ describe('saveFile hooks', () => {
);
});
it('beforeSave(Parse.File) should change values by returning new fileObject', async () => {
it('beforeSaveFile should change values by returning new fileObject', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
Parse.Cloud.beforeSave(Parse.File, async req => {
@@ -3727,7 +3727,7 @@ describe('saveFile hooks', () => {
expect(file._name.indexOf(expectedFileName)).toBe(file._name.length - expectedFileName.length);
});
it('beforeSave(Parse.File) should contain metadata and tags saved from client', async () => {
it('beforeSaveFile should contain metadata and tags saved from client', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
Parse.Cloud.beforeSave(Parse.File, async req => {
@@ -3755,7 +3755,7 @@ describe('saveFile hooks', () => {
);
});
it('beforeSave(Parse.File) should return same file data with new file name', async () => {
it('beforeSaveFile should return same file data with new file name', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const config = Config.get('test');
config.filesController.options.preserveFileName = true;
@@ -3770,7 +3770,7 @@ describe('saveFile hooks', () => {
expect(result.name()).toBe('2020-04-01.txt');
});
it('afterSave(Parse.File) should set fileSize to null if beforeSave returns an already saved file', async () => {
it('afterSaveFile should set fileSize to null if beforeSave returns an already saved file', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
Parse.Cloud.beforeSave(Parse.File, req => {
@@ -3790,7 +3790,7 @@ describe('saveFile hooks', () => {
expect(createFileSpy).not.toHaveBeenCalled();
});
it('afterSave(Parse.File) should throw error', async () => {
it('afterSaveFile should throw error', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.afterSave(Parse.File, async () => {
throw new Parse.Error(400, 'some-error-message');
@@ -3804,7 +3804,7 @@ describe('saveFile hooks', () => {
}
});
it('afterSave(Parse.File) should call with fileObject', async done => {
it('afterSaveFile should call with fileObject', async done => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeSave(Parse.File, async req => {
req.file.setTags({ tagA: 'some-tag' });
@@ -3820,7 +3820,7 @@ describe('saveFile hooks', () => {
await file.save({ useMasterKey: true });
});
it('afterSave(Parse.File) should change fileSize when file data changes', async done => {
it('afterSaveFile should change fileSize when file data changes', async done => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeSave(Parse.File, async req => {
expect(req.fileSize).toBe(3);
@@ -3837,7 +3837,7 @@ describe('saveFile hooks', () => {
await file.save({ useMasterKey: true });
});
it('beforeDelete(Parse.File) should call with fileObject', async () => {
it('beforeDeleteFile should call with fileObject', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeDelete(Parse.File, req => {
expect(req.file).toBeInstanceOf(Parse.File);
@@ -3849,7 +3849,7 @@ describe('saveFile hooks', () => {
await file.destroy({ useMasterKey: true });
});
it('beforeDelete(Parse.File) should throw error', async done => {
it('beforeDeleteFile should throw error', async done => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeDelete(Parse.File, () => {
throw new Error('some error message');
@@ -3863,7 +3863,7 @@ describe('saveFile hooks', () => {
}
});
it('afterDelete(Parse.File) should call with fileObject', async done => {
it('afterDeleteFile should call with fileObject', async done => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeDelete(Parse.File, req => {
expect(req.file).toBeInstanceOf(Parse.File);
@@ -3880,7 +3880,7 @@ describe('saveFile hooks', () => {
await file.destroy({ useMasterKey: true });
});
it('beforeSave(Parse.File) should not change file if nothing is returned', async () => {
it('beforeSaveFile should not change file if nothing is returned', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
Parse.Cloud.beforeSave(Parse.File, () => {
return;
@@ -3890,7 +3890,7 @@ describe('saveFile hooks', () => {
expect(result).toBe(file);
});
it('throw custom error from beforeSave(Parse.File) ', async done => {
it('throw custom error from beforeSaveFile', async done => {
Parse.Cloud.beforeSave(Parse.File, () => {
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'It should fail');
});
@@ -3904,7 +3904,7 @@ describe('saveFile hooks', () => {
}
});
it('throw empty error from beforeSave(Parse.File)', async done => {
it('throw empty error from beforeSaveFile', async done => {
Parse.Cloud.beforeSave(Parse.File, () => {
throw null;
});
@@ -3917,6 +3917,55 @@ describe('saveFile hooks', () => {
done();
}
});
it('legacy hooks', async () => {
await reconfigureServer({ filesAdapter: mockAdapter });
const logger = require('../lib/logger').logger;
const logSpy = spyOn(logger, 'warn').and.callFake(() => {});
const triggers = {
beforeSaveFile(req) {
req.file.setTags({ tagA: 'some-tag' });
req.file.setMetadata({ foo: 'bar' });
expect(req.triggerName).toEqual('beforeSave');
expect(req.master).toBe(true);
},
afterSaveFile(req) {
expect(req.master).toBe(true);
expect(req.file._tags).toEqual({ tagA: 'some-tag' });
expect(req.file._metadata).toEqual({ foo: 'bar' });
},
beforeDeleteFile(req) {
expect(req.file).toBeInstanceOf(Parse.File);
expect(req.file._name).toEqual('popeye.txt');
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
expect(req.fileSize).toBe(null);
},
afterDeleteFile(req) {
expect(req.file).toBeInstanceOf(Parse.File);
expect(req.file._name).toEqual('popeye.txt');
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
},
};
for (const key in triggers) {
spyOn(triggers, key).and.callThrough();
Parse.Cloud[key](triggers[key]);
}
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
await file.save({ useMasterKey: true });
await new Parse.File('popeye.txt', [1, 2, 3], 'text/plain').destroy({ useMasterKey: true });
await new Promise(resolve => setTimeout(resolve, 100));
for (const key in triggers) {
expect(triggers[key]).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(
`DeprecationWarning: Parse.Cloud.${key} is deprecated and will be removed in a future version. Use Parse.Cloud.${key.replace(
'File',
''
)}(Parse.File, (request) => {})`
);
}
});
});
describe('sendEmail', () => {
+9 -95
View File
@@ -3,7 +3,6 @@
const Auth = require('../lib/Auth');
const Config = require('../lib/Config');
const request = require('../lib/request');
const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
describe('Email Verification Token Expiration: ', () => {
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
@@ -127,7 +126,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
request({
url: sendEmailOptions.link,
@@ -169,7 +167,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
request({
url: sendEmailOptions.link,
@@ -217,7 +214,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
request({
url: sendEmailOptions.link,
@@ -303,7 +299,7 @@ describe('Email Verification Token Expiration: ', () => {
};
const verifyUserEmails = {
method(req) {
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']);
return false;
},
};
@@ -362,7 +358,7 @@ describe('Email Verification Token Expiration: ', () => {
};
const verifyUserEmails = {
method(req) {
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']);
if (req.object.get('username') === 'no_email') {
return false;
}
@@ -391,10 +387,9 @@ describe('Email Verification Token Expiration: ', () => {
user2.setPassword('expiringToken');
user2.set('email', 'user2@example.com');
await user2.signUp();
await jasmine.timeout();
expect(user2.getSessionToken()).toBeUndefined();
expect(sendEmailOptions).toBeDefined();
expect(verifySpy).toHaveBeenCalledTimes(5);
expect(verifySpy).toHaveBeenCalledTimes(4);
});
it('can conditionally send user email verification', async () => {
@@ -426,47 +421,10 @@ describe('Email Verification Token Expiration: ', () => {
newUser.set('email', 'user@example.com');
await newUser.signUp();
await Parse.User.requestEmailVerification('user@example.com');
await jasmine.timeout();
expect(sendSpy).toHaveBeenCalledTimes(2);
expect(emailSpy).toHaveBeenCalledTimes(0);
});
it('provides full user object in email verification function on email and username change', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
const sendVerificationEmail = {
method(req) {
expect(req.user).toBeDefined();
expect(req.user.id).toBeDefined();
expect(req.user.get('createdAt')).toBeDefined();
expect(req.user.get('updatedAt')).toBeDefined();
expect(req.master).toBeDefined();
return false;
},
};
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5,
publicServerURL: 'http://localhost:8378/1',
sendUserEmailVerification: sendVerificationEmail.method,
});
const user = new Parse.User();
user.setPassword('password');
user.setUsername('new@example.com');
user.setEmail('user@example.com');
await user.save(null, { useMasterKey: true });
// Update email and username
user.setUsername('new@example.com');
user.setEmail('new@example.com');
await user.save(null, { useMasterKey: true });
});
it('beforeSave options do not change existing behaviour', async () => {
let sendEmailOptions;
const emailAdapter = {
@@ -489,7 +447,6 @@ describe('Email Verification Token Expiration: ', () => {
newUser.setPassword('expiringToken');
newUser.set('email', 'user@parse.com');
await newUser.signUp();
await jasmine.timeout();
const response = await request({
url: sendEmailOptions.link,
followRedirects: false,
@@ -532,7 +489,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
request({
url: sendEmailOptions.link,
@@ -592,7 +548,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
return request({
url: sendEmailOptions.link,
@@ -810,9 +765,6 @@ describe('Email Verification Token Expiration: ', () => {
})
.then(response => {
expect(response.status).toBe(200);
})
.then(() => jasmine.timeout())
.then(() => {
expect(sendVerificationEmailCallCount).toBe(2);
expect(sendEmailOptions).toBeDefined();
@@ -842,41 +794,6 @@ describe('Email Verification Token Expiration: ', () => {
});
});
it('provides function arguments in verifyUserEmails on verificationEmailRequest', async () => {
const user = new Parse.User();
user.setUsername('user');
user.setPassword('pass');
user.set('email', 'test@example.com');
await user.signUp();
const verifyUserEmails = {
method: async (params) => {
expect(params.object).toBeInstanceOf(Parse.User);
expect(params.ip).toBeDefined();
expect(params.master).toBeDefined();
expect(params.installationId).toBeDefined();
expect(params.resendRequest).toBeTrue();
return true;
},
};
const verifyUserEmailsSpy = spyOn(verifyUserEmails, 'method').and.callThrough();
await reconfigureServer({
appName: 'test',
publicServerURL: 'http://localhost:1337/1',
verifyUserEmails: verifyUserEmails.method,
preventLoginWithUnverifiedEmail: verifyUserEmails.method,
preventSignupWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
});
await expectAsync(Parse.User.requestEmailVerification('test@example.com')).toBeResolved();
expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(1);
});
it('should throw with invalid emailVerifyTokenReuseIfValid', async done => {
const sendEmailOptions = [];
const emailAdapter = {
@@ -944,7 +861,7 @@ describe('Email Verification Token Expiration: ', () => {
const config = Config.get('test');
const [userBeforeRequest] = await config.database.find('_User', {
username: 'resends_verification_token',
}, {}, Auth.maintenance(config));
});
// store this user before we make our email request
expect(sendVerificationEmailCallCount).toBe(1);
await new Promise(resolve => {
@@ -964,21 +881,20 @@ describe('Email Verification Token Expiration: ', () => {
'Content-Type': 'application/json',
},
});
await jasmine.timeout();
expect(response.status).toBe(200);
expect(sendVerificationEmailCallCount).toBe(2);
expect(sendEmailOptions).toBeDefined();
const [userAfterRequest] = await config.database.find('_User', {
username: 'resends_verification_token',
}, {}, Auth.maintenance(config));
});
// Verify that token & expiration haven't been changed for this new request
// verify that our token & expiration has been changed for this new request
expect(typeof userAfterRequest).toBe('object');
expect(userBeforeRequest._email_verify_token).toBeDefined();
expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token);
expect(userBeforeRequest._email_verify_token_expires_at).toBeDefined();
expect(userBeforeRequest._email_verify_token_expires_at).toEqual(userAfterRequest._email_verify_token_expires_at);
expect(userBeforeRequest._email_verify_token_expires_at).toEqual(
userAfterRequest._email_verify_token_expires_at
);
done();
});
@@ -1007,7 +923,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
return request({
url: sendEmailOptions.link,
@@ -1246,7 +1161,6 @@ describe('Email Verification Token Expiration: ', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
request({
url: sendEmailOptions.link,
+17 -3
View File
@@ -284,11 +284,25 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
amount: 1,
},
};
await Parse.Server.database.update('MyClass', query, update, { upsert: true });
await Parse.Server.database.update(
'MyClass',
query,
update,
{ upsert: true },
);
update.objectId.amount = uuid2;
await Parse.Server.database.update('MyClass', query, update, { upsert: true });
await Parse.Server.database.update(
'MyClass',
query,
update,
{ upsert: true },
);
const res = await Parse.Server.database.find(schema.className, {}, {});
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);
+1 -6
View File
@@ -749,7 +749,6 @@ describe('Pages Router', () => {
user.setPassword('examplePassword');
user.set('email', 'mail@example.com');
await user.signUp();
await jasmine.timeout();
const link = sendVerificationEmail.calls.all()[0].args[0].link;
const linkWithLocale = new URL(link);
@@ -778,7 +777,6 @@ describe('Pages Router', () => {
user.setPassword('examplePassword');
user.set('email', 'mail@example.com');
await user.signUp();
await jasmine.timeout();
const link = sendVerificationEmail.calls.all()[0].args[0].link;
const linkWithLocale = new URL(link);
@@ -832,7 +830,6 @@ describe('Pages Router', () => {
user.setPassword('examplePassword');
user.set('email', 'mail@example.com');
await user.signUp();
await jasmine.timeout();
const link = sendVerificationEmail.calls.all()[0].args[0].link;
const linkWithLocale = new URL(link);
@@ -849,8 +846,6 @@ describe('Pages Router', () => {
const locale = linkResponse.headers['x-parse-page-param-locale'];
const username = linkResponse.headers['x-parse-page-param-username'];
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
await jasmine.timeout();
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
expect(appId).toBeDefined();
expect(locale).toBe(exampleLocale);
@@ -1195,7 +1190,6 @@ describe('Pages Router', () => {
user.setPassword('examplePassword');
user.set('email', 'mail@example.com');
await user.signUp();
await jasmine.timeout();
const link = sendVerificationEmail.calls.all()[0].args[0].link;
const linkResponse = await request({
@@ -1203,6 +1197,7 @@ describe('Pages Router', () => {
followRedirects: false,
});
expect(linkResponse.status).toBe(200);
const pagePath = pageResponse.calls.all()[0].args[0];
expect(pagePath).toMatch(new RegExp(`\/${pages.emailVerificationSuccess.defaultFile}`));
});
+3 -1
View File
@@ -2,12 +2,14 @@
const request = require('../lib/request');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const pushCompleted = async pushId => {
const query = new Parse.Query('_PushStatus');
query.equalTo('objectId', pushId);
let result = await query.first({ useMasterKey: true });
while (!(result && result.get('status') === 'succeeded')) {
await jasmine.timeout();
await sleep(100);
result = await query.first({ useMasterKey: true });
}
};
-1
View File
@@ -1285,7 +1285,6 @@ describe('miscellaneous', function () {
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);
+3 -111
View File
@@ -6832,7 +6832,7 @@ describe('ParseGraphQLServer', () => {
describe('Files Mutations', () => {
describe('Create', () => {
it('should return File object', async () => {
it_only_node_version('<17')('should return File object', async () => {
const clientMutationId = uuidv4();
parseServer = await global.reconfigureServer({
@@ -9298,7 +9298,7 @@ describe('ParseGraphQLServer', () => {
expect(result6[0].node.name).toEqual('imACountry3');
});
it('should support files', async () => {
it_only_node_version('<17')('should support files', async () => {
try {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
@@ -9546,115 +9546,7 @@ describe('ParseGraphQLServer', () => {
}
});
it('should support file upload for on fly creation through pointer and relation', async () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});
const schema = new Parse.Schema('SomeClass');
schema.addFile('someFileField');
schema.addPointer('somePointerField', 'SomeClass');
schema.addRelation('someRelationField', 'SomeClass');
await schema.save();
const body = new FormData();
body.append(
'operations',
JSON.stringify({
query: `
mutation UploadFiles(
$fields: CreateSomeClassFieldsInput
) {
createSomeClass(
input: { fields: $fields }
) {
someClass {
id
someFileField {
name
url
}
somePointerField {
id
someFileField {
name
url
}
}
someRelationField {
edges {
node {
id
someFileField {
name
url
}
}
}
}
}
}
}
`,
variables: {
fields: {
someFileField: { upload: null },
somePointerField: {
createAndLink: {
someFileField: { upload: null },
},
},
someRelationField: {
createAndAdd: [
{
someFileField: { upload: null },
},
],
},
},
},
})
);
body.append(
'map',
JSON.stringify({
1: ['variables.fields.someFileField.upload'],
2: ['variables.fields.somePointerField.createAndLink.someFileField.upload'],
3: ['variables.fields.someRelationField.createAndAdd.0.someFileField.upload'],
})
);
body.append('1', 'My File Content someFileField', {
filename: 'someFileField.txt',
contentType: 'text/plain',
});
body.append('2', 'My File Content somePointerField', {
filename: 'somePointerField.txt',
contentType: 'text/plain',
});
body.append('3', 'My File Content someRelationField', {
filename: 'someRelationField.txt',
contentType: 'text/plain',
});
const res = await fetch('http://localhost:13377/graphql', {
method: 'POST',
headers,
body,
});
expect(res.status).toEqual(200);
const result = await res.json();
console.log(result);
expect(result.data.createSomeClass.someClass.someFileField.name).toEqual(
jasmine.stringMatching(/_someFileField.txt$/)
);
expect(result.data.createSomeClass.someClass.somePointerField.someFileField.name).toEqual(
jasmine.stringMatching(/_somePointerField.txt$/)
);
expect(
result.data.createSomeClass.someClass.someRelationField.edges[0].node.someFileField.name
).toEqual(jasmine.stringMatching(/_someRelationField.txt$/));
});
it('should not upload if file is too large', async () => {
it_only_node_version('<17')('should not upload if file is too large', async () => {
parseGraphQLServer.parseServer.config.maxUploadSize = '1kb';
const body = new FormData();
+34 -1
View File
@@ -190,7 +190,7 @@ describe('Hooks', () => {
it('should fail trying to create two times the same function', done => {
Parse.Hooks.createFunction('my_new_function', 'http://url.com')
.then(() => jasmine.timeout())
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
.then(
() => {
return Parse.Hooks.createFunction('my_new_function', 'http://url.com');
@@ -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/);
}
});
});
-2
View File
@@ -86,7 +86,6 @@ describe_only_db('mongo')('Parse.Query hint', () => {
let result = await collection.aggregate([{ $group: { _id: '$foo' } }], {
explain: true,
});
let { queryPlanner } = result[0].stages[0].$cursor;
expect(queryPlanner.winningPlan.stage).toBe('COLLSCAN');
@@ -94,7 +93,6 @@ describe_only_db('mongo')('Parse.Query hint', () => {
hint: '_id_',
explain: true,
});
queryPlanner = result[0].stages[0].$cursor.queryPlanner;
expect(queryPlanner.winningPlan.stage).toBe('FETCH');
expect(queryPlanner.winningPlan.inputStage.indexName).toBe('_id_');
-30
View File
@@ -3067,7 +3067,6 @@ describe('Parse.User testing', () => {
},
});
})
.then(() => jasmine.timeout())
.then(() => {
expect(emailCalled).toBe(true);
expect(emailOptions).not.toBeUndefined();
@@ -3225,35 +3224,6 @@ describe('Parse.User testing', () => {
.catch(done.fail);
});
it('should return current session with expired expiration date', async () => {
await Parse.User.signUp('buser', 'somepass', null);
const response = await request({
method: 'GET',
url: 'http://localhost:8378/1/classes/_Session',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
});
const body = response.data;
const id = body.results[0].objectId;
const expiresAt = new Date(new Date().setYear(2015));
await request({
method: 'PUT',
url: 'http://localhost:8378/1/classes/_Session/' + id,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
},
body: {
expiresAt: { __type: 'Date', iso: expiresAt.toISOString() },
},
});
const session = await Parse.Session.current();
expect(session.get('expiresAt')).toEqual(expiresAt);
});
it('should not create extraneous session tokens', done => {
const config = Config.get(Parse.applicationId);
config.database
+6 -4
View File
@@ -26,12 +26,14 @@ const successfulIOS = function (body, installations) {
return Promise.all(promises);
};
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const pushCompleted = async pushId => {
const query = new Parse.Query('_PushStatus');
query.equalTo('objectId', pushId);
let result = await query.first({ useMasterKey: true });
while (!(result && result.get('status') === 'succeeded')) {
await jasmine.timeout();
await sleep(100);
result = await query.first({ useMasterKey: true });
}
};
@@ -560,7 +562,7 @@ describe('PushController', () => {
});
const pushStatusId = await sendPush(payload, {}, config, auth);
// it is enqueued so it can take time
await jasmine.timeout(1000);
await sleep(1000);
Parse.serverURL = 'http://localhost:8378/1'; // GOOD url
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result).toBeDefined();
@@ -799,7 +801,7 @@ describe('PushController', () => {
});
await Parse.Object.saveAll(installations);
await pushController.sendPush(payload, {}, config, auth);
await jasmine.timeout(1000);
await sleep(1000);
const query = new Parse.Query('_PushStatus');
const results = await query.find({ useMasterKey: true });
expect(results.length).toBe(1);
@@ -854,7 +856,7 @@ describe('PushController', () => {
const config = Config.get(Parse.applicationId);
await Parse.Object.saveAll(installations);
await pushController.sendPush(payload, {}, config, auth);
await jasmine.timeout(1000);
await sleep(1000);
const query = new Parse.Query('_PushStatus');
const results = await query.find({ useMasterKey: true });
expect(results.length).toBe(1);
+22 -44
View File
@@ -1,12 +1,16 @@
const emailAdapter = require('./support/MockEmailAdapter');
const Config = require('../lib/Config');
const Auth = require('../lib/Auth');
describe('UserController', () => {
const user = {
_email_verify_token: 'testToken',
username: 'testUser',
email: 'test@example.com',
};
describe('sendVerificationEmail', () => {
describe('parseFrameURL not provided', () => {
it('uses publicServerURL', async () => {
await reconfigureServer({
it('uses publicServerURL', async done => {
const server = await reconfigureServer({
publicServerURL: 'http://www.example.com',
customPages: {
parseFrameURL: undefined,
@@ -15,33 +19,20 @@ describe('UserController', () => {
emailAdapter,
appName: 'test',
});
let emailOptions;
emailAdapter.sendVerificationEmail = options => {
emailOptions = options;
return Promise.resolve();
expect(options.link).toEqual(
'http://www.example.com/apps/test/verify_email?token=testToken&username=testUser'
);
emailAdapter.sendVerificationEmail = () => Promise.resolve();
done();
};
const username = 'verificationUser';
const user = new Parse.User();
user.setUsername(username);
user.setPassword('pass');
user.setEmail('verification@example.com');
await user.signUp();
const config = Config.get('test');
const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config));
const rawUsername = rawUser[0].username;
const rawToken = rawUser[0]._email_verify_token;
expect(rawToken).toBeDefined();
expect(rawUsername).toBe(username);
expect(emailOptions.link).toEqual(`http://www.example.com/apps/test/verify_email?token=${rawToken}&username=${username}`);
server.config.userController.sendVerificationEmail(user);
});
});
describe('parseFrameURL provided', () => {
it('uses parseFrameURL and includes the destination in the link parameter', async () => {
await reconfigureServer({
it('uses parseFrameURL and includes the destination in the link parameter', async done => {
const server = await reconfigureServer({
publicServerURL: 'http://www.example.com',
customPages: {
parseFrameURL: 'http://someother.example.com/handle-parse-iframe',
@@ -50,27 +41,14 @@ describe('UserController', () => {
emailAdapter,
appName: 'test',
});
let emailOptions;
emailAdapter.sendVerificationEmail = options => {
emailOptions = options;
return Promise.resolve();
expect(options.link).toEqual(
'http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser'
);
emailAdapter.sendVerificationEmail = () => Promise.resolve();
done();
};
const username = 'verificationUser';
const user = new Parse.User();
user.setUsername(username);
user.setPassword('pass');
user.setEmail('verification@example.com');
await user.signUp();
const config = Config.get('test');
const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config));
const rawUsername = rawUser[0].username;
const rawToken = rawUser[0]._email_verify_token;
expect(rawToken).toBeDefined();
expect(rawUsername).toBe(username);
expect(emailOptions.link).toEqual(`http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=${rawToken}&username=${username}`);
server.config.userController.sendVerificationEmail(user);
});
});
});
-66
View File
@@ -51,7 +51,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
user.setUsername('zxcv');
user.setEmail('testIfEnabled@parse.com');
await user.signUp();
await jasmine.timeout();
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
user.fetch().then(() => {
expect(user.get('emailVerified')).toEqual(false);
@@ -138,7 +137,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
spyOn(emailAdapter, 'sendVerificationEmail').and.callFake(options => {
expect(options.link).not.toBeNull();
expect(options.link).not.toMatch(/token=undefined/);
expect(options.link).not.toMatch(/username=undefined/);
Promise.resolve();
});
const user = new Parse.User();
@@ -185,7 +183,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
user.setUsername('zxcv');
user.set('email', 'testSendSimpleAdapter@parse.com');
await user.signUp();
await jasmine.timeout();
expect(calls).toBe(1);
user
.fetch()
@@ -245,66 +242,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
});
});
it('prevents user from signup and login if email is not verified and preventLoginWithUnverifiedEmail is set to function returning true', async () => {
await reconfigureServer({
appName: 'test',
publicServerURL: 'http://localhost:1337/1',
verifyUserEmails: async () => true,
preventLoginWithUnverifiedEmail: async () => true,
preventSignupWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
});
const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('zxcv');
user.set('email', 'testInvalidConfig@parse.com');
const signupRes = await user.signUp(null).catch(e => e);
expect(signupRes.message).toEqual('User email is not verified.');
const loginRes = await Parse.User.logIn('zxcv', 'asdf').catch(e => e);
expect(loginRes.message).toEqual('User email is not verified.');
});
it('provides function arguments in verifyUserEmails on login', async () => {
const user = new Parse.User();
user.setUsername('user');
user.setPassword('pass');
user.set('email', 'test@example.com');
await user.signUp();
const verifyUserEmails = {
method: async (params) => {
expect(params.object).toBeInstanceOf(Parse.User);
expect(params.ip).toBeDefined();
expect(params.master).toBeDefined();
expect(params.installationId).toBeDefined();
return true;
},
};
const verifyUserEmailsSpy = spyOn(verifyUserEmails, 'method').and.callThrough();
await reconfigureServer({
appName: 'test',
publicServerURL: 'http://localhost:1337/1',
verifyUserEmails: verifyUserEmails.method,
preventLoginWithUnverifiedEmail: verifyUserEmails.method,
preventSignupWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
});
const res = await Parse.User.logIn('user', 'pass').catch(e => e);
expect(res.code).toBe(205);
expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(2);
});
it('allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', async () => {
let sendEmailOptions;
const emailAdapter = {
@@ -326,7 +263,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
user.setUsername('user');
user.set('email', 'user@example.com');
await user.signUp();
await jasmine.timeout();
expect(sendEmailOptions).not.toBeUndefined();
const response = await request({
url: sendEmailOptions.link,
@@ -638,7 +574,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
user.setUsername('zxcv');
user.set('email', 'user@parse.com');
await user.signUp();
await jasmine.timeout();
expect(emailSent).toBe(true);
done();
});
@@ -666,7 +601,6 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
user.set('email', 'user@parse.com');
return user.signUp();
})
.then(() => jasmine.timeout())
.then(() => {
expect(sendEmailOptions).not.toBeUndefined();
request({
-79
View File
@@ -585,83 +585,4 @@ describe('Verify User Password', () => {
done();
});
});
it('verify password of user with unverified email with master key and ignoreEmailVerification=true', async () => {
await reconfigureServer({
publicServerURL: 'http://localhost:8378/',
appName: 'emailVerify',
verifyUserEmails: true,
preventLoginWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
});
const user = new Parse.User();
user.setUsername('user');
user.setPassword('pass');
user.setEmail('test@example.com');
await user.signUp();
const { data: res } = await request({
method: 'POST',
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Master-Key': Parse.masterKey,
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {
username: 'user',
password: 'pass',
ignoreEmailVerification: true,
},
json: true,
});
expect(res.objectId).toBe(user.id);
expect(Object.prototype.hasOwnProperty.call(res, 'sessionToken')).toEqual(false);
expect(Object.prototype.hasOwnProperty.call(res, 'password')).toEqual(false);
});
it('fails to verify password of user with unverified email with master key and ignoreEmailVerification=false', async () => {
await reconfigureServer({
publicServerURL: 'http://localhost:8378/',
appName: 'emailVerify',
verifyUserEmails: true,
preventLoginWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
});
const user = new Parse.User();
user.setUsername('user');
user.setPassword('pass');
user.setEmail('test@example.com');
await user.signUp();
const res = await request({
method: 'POST',
url: Parse.serverURL + '/verifyPassword',
headers: {
'X-Parse-Master-Key': Parse.masterKey,
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
body: {
username: 'user',
password: 'pass',
ignoreEmailVerification: false,
},
json: true,
}).catch(e => e);
expect(res.status).toBe(400);
expect(res.text).toMatch(/User email is not verified/);
});
});
+3 -13
View File
@@ -251,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
@@ -570,16 +570,6 @@ global.describe_only_db = db => {
}
};
global.fdescribe_only_db = db => {
if (process.env.PARSE_SERVER_TEST_DB == db) {
return fdescribe;
} else if (!process.env.PARSE_SERVER_TEST_DB && db == 'mongo') {
return fdescribe;
} else {
return xdescribe;
}
};
global.describe_only = validator => {
if (validator()) {
return describe;
@@ -605,4 +595,4 @@ jasmine.restoreLibrary = function (library, name) {
require(library)[name] = libraryCache[library][name];
};
jasmine.timeout = (t = 100) => new Promise(resolve => setTimeout(resolve, t));
jasmine.timeout = t => new Promise(resolve => setTimeout(resolve, t));
+8 -6
View File
@@ -143,10 +143,10 @@ describe('rest create', () => {
const res = await request({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-REST-API-Key': 'rest'
},
method: 'GET',
url: `http://localhost:8378/1/classes/TestObject/${id}`,
url: `http://localhost:8378/1/classes/TestObject/${id}`
});
return res.data;
@@ -158,10 +158,10 @@ describe('rest create', () => {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Maintenance-Key': 'testing',
'X-Parse-Maintenance-Key': 'testing'
},
method: 'POST',
url: 'http://localhost:8378/1/classes/TestObject',
url: 'http://localhost:8378/1/classes/TestObject'
};
});
@@ -206,7 +206,8 @@ describe('rest create', () => {
try {
await request(req);
fail();
} catch (err) {
}
catch (err) {
expect(err.data.code).toEqual(Parse.Error.VALIDATION_ERROR);
}
});
@@ -229,7 +230,8 @@ describe('rest create', () => {
try {
await request(req);
fail();
} catch (err) {
}
catch (err) {
expect(err.data.code).toEqual(Parse.Error.INCORRECT_TYPE);
}
});
+51
View File
@@ -235,6 +235,32 @@ describe('Vulnerabilities', () => {
await new Promise(resolve => server.close(resolve));
});
it('allows BSON type code data in write request with custom denylist', async () => {
await reconfigureServer({
requestKeywordDenylist: [],
});
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
};
const params = {
headers: headers,
method: 'POST',
url: 'http://localhost:8378/1/classes/RCE',
body: JSON.stringify({
obj: {
_bsontype: 'Code',
code: 'delete Object.prototype.evalFunctions',
},
}),
};
const response = await request(params).catch(e => e);
expect(response.status).toBe(201);
const text = JSON.parse(response.text);
expect(text.objectId).toBeDefined();
});
it('denies write request with custom denylist of key/value', async () => {
await reconfigureServer({
requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }],
@@ -433,3 +459,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);
});
});
+4 -1
View File
@@ -3,6 +3,7 @@
const Parse = require('parse/node').Parse;
const jwksClient = require('jwks-rsa');
const util = require('util');
const jwt = require('jsonwebtoken');
const authUtils = require('./utils');
@@ -16,9 +17,11 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
cacheMaxAge,
});
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
let key;
try {
key = await authUtils.getSigningKey(client, keyId);
key = await asyncGetSigningKeyFunction(keyId);
} catch (error) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
+4 -1
View File
@@ -2,6 +2,7 @@
const Parse = require('parse/node').Parse;
const crypto = require('crypto');
const jwksClient = require('jwks-rsa');
const util = require('util');
const jwt = require('jsonwebtoken');
const httpsRequest = require('./httpsRequest');
const authUtils = require('./utils');
@@ -59,9 +60,11 @@ const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
cacheMaxAge,
});
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
let key;
try {
key = await authUtils.getSigningKey(client, keyId);
key = await asyncGetSigningKeyFunction(keyId);
} catch (error) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
-11
View File
@@ -1,5 +1,4 @@
const jwt = require('jsonwebtoken');
const util = require('util');
const Parse = require('parse/node').Parse;
const getHeaderFromToken = token => {
const decodedToken = jwt.decode(token, { complete: true });
@@ -9,16 +8,6 @@ const getHeaderFromToken = token => {
return decodedToken.header;
};
/**
* Returns the signing key from a JWKS client.
* @param {Object} client The JWKS client.
* @param {String} key The kid.
*/
async function getSigningKey(client, key) {
return util.promisify(client.getSigningKey)(key);
}
module.exports = {
getHeaderFromToken,
getSigningKey,
};
+12 -4
View File
@@ -160,10 +160,18 @@ export default class MongoCollection {
}
_ensureSparseUniqueIndexInBackground(indexRequest) {
return this._mongoCollection.createIndex(indexRequest, {
unique: true,
background: true,
sparse: true,
return new Promise((resolve, reject) => {
this._mongoCollection.createIndex(
indexRequest,
{ unique: true, background: true, sparse: true },
error => {
if (error) {
reject(error);
} else {
resolve();
}
}
);
});
}
@@ -172,6 +172,7 @@ export class MongoStorageAdapter implements StorageAdapter {
// parsing and re-formatting causes the auth value (if there) to get URI
// encoded
const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions)
.then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
@@ -686,8 +687,13 @@ export class MongoStorageAdapter implements StorageAdapter {
};
return this._adaptiveCollection(className)
.then(collection =>
collection._mongoCollection.createIndex(indexCreationRequest, indexOptions)
.then(
collection =>
new Promise((resolve, reject) =>
collection._mongoCollection.createIndex(indexCreationRequest, indexOptions, error =>
error ? reject(error) : resolve()
)
)
)
.catch(err => this.handleError(err));
}
@@ -457,7 +457,6 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) =>
);
}
value = mapValues(restValue, transformInteriorValue);
return { key: restKey, value };
};
@@ -2656,7 +2656,7 @@ function literalizeRegexPart(s: string) {
.replace(/([^\\])(\\Q)/, '$1')
.replace(/^\\E/, '')
.replace(/^\\Q/, '')
.replace(/([^'])'/, `$1''`)
.replace(/([^'])'/g, `$1''`)
.replace(/^'([^'])/, `''$1`);
}
+20 -22
View File
@@ -36,10 +36,11 @@ export class UserController extends AdaptableController {
}
async setEmailVerifyToken(user, req, storage = {}) {
const shouldSendEmail =
this.shouldVerifyEmails === true ||
(typeof this.shouldVerifyEmails === 'function' &&
(await Promise.resolve(this.shouldVerifyEmails(req))) === true);
let shouldSendEmail = this.shouldVerifyEmails;
if (typeof shouldSendEmail === 'function') {
const response = await Promise.resolve(shouldSendEmail(req));
shouldSendEmail = response !== false;
}
if (!shouldSendEmail) {
return false;
}
@@ -129,6 +130,9 @@ export class UserController extends AdaptableController {
}
async getUserIfNeeded(user) {
if (user.username && user.email) {
return Promise.resolve(user);
}
var where = {};
if (user.username) {
where.username = user.username;
@@ -145,11 +149,12 @@ export class UserController extends AdaptableController {
className: '_User',
restWhere: where,
});
const result = await query.execute();
if (result.results.length != 1) {
throw undefined;
}
return result.results[0];
return query.execute().then(function (result) {
if (result.results.length != 1) {
throw undefined;
}
return result.results[0];
});
}
async sendVerificationEmail(user, req) {
@@ -157,8 +162,7 @@ export class UserController extends AdaptableController {
return;
}
const token = encodeURIComponent(user._email_verify_token);
// We may need to fetch the user in case of update email; only use the `fetchedUser`
// from this point onwards; do not use the `user` as it may not contain all fields.
// We may need to fetch the user in case of update email
const fetchedUser = await this.getUserIfNeeded(user);
let shouldSendEmail = this.config.sendUserEmailVerification;
if (typeof shouldSendEmail === 'function') {
@@ -173,7 +177,7 @@ export class UserController extends AdaptableController {
if (!shouldSendEmail) {
return;
}
const username = encodeURIComponent(fetchedUser.username);
const username = encodeURIComponent(user.username);
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
const options = {
@@ -194,7 +198,7 @@ export class UserController extends AdaptableController {
* @param user
* @returns {*}
*/
async regenerateEmailVerifyToken(user, master, installationId, ip) {
async regenerateEmailVerifyToken(user, master) {
const { _email_verify_token } = user;
let { _email_verify_token_expires_at } = user;
if (_email_verify_token_expires_at && _email_verify_token_expires_at.__type === 'Date') {
@@ -206,15 +210,9 @@ export class UserController extends AdaptableController {
_email_verify_token &&
new Date() < new Date(_email_verify_token_expires_at)
) {
return Promise.resolve(true);
return Promise.resolve();
}
const shouldSend = await this.setEmailVerifyToken(user, {
object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
master,
installationId,
ip,
resendRequest: true
});
const shouldSend = await this.setEmailVerifyToken(user, { user, master });
if (!shouldSend) {
return;
}
@@ -226,7 +224,7 @@ export class UserController extends AdaptableController {
if (!aUser || aUser.emailVerified) {
throw undefined;
}
const generate = await this.regenerateEmailVerifyToken(aUser, req.auth?.isMaster, req.auth?.installationId, req.ip);
const generate = await this.regenerateEmailVerifyToken(aUser, req.auth?.isMaster);
if (generate) {
this.sendVerificationEmail(aUser, req);
}
@@ -82,7 +82,6 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
const parseFields = await transformTypes('create', fields, {
className,
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});
@@ -191,7 +190,6 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
const parseFields = await transformTypes('update', fields, {
className,
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});
-2
View File
@@ -38,7 +38,6 @@ const load = parseGraphQLSchema => {
const parseFields = await transformTypes('create', fields, {
className: '_User',
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});
@@ -115,7 +114,6 @@ const load = parseGraphQLSchema => {
const parseFields = await transformTypes('create', fields, {
className: '_User',
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});
+5 -25
View File
@@ -7,7 +7,7 @@ import * as objectsMutations from '../helpers/objectsMutations';
const transformTypes = async (
inputType: 'create' | 'update',
fields,
{ className, parseGraphQLSchema, req, originalFields }
{ className, parseGraphQLSchema, req }
) => {
const {
classGraphQLCreateType,
@@ -44,16 +44,13 @@ const transformTypes = async (
fields[field] = transformers.polygon(fields[field]);
break;
case inputTypeField.type === defaultGraphQLTypes.FILE_INPUT:
// Use `originalFields` to handle file upload since fields are a deepcopy and do not
// keep the file object
fields[field] = await transformers.file(originalFields[field], req);
fields[field] = await transformers.file(fields[field], req);
break;
case parseClass.fields[field].type === 'Relation':
fields[field] = await transformers.relation(
parseClass.fields[field].targetClass,
field,
fields[field],
originalFields[field],
parseGraphQLSchema,
req
);
@@ -67,7 +64,6 @@ const transformTypes = async (
parseClass.fields[field].targetClass,
field,
fields[field],
originalFields[field],
parseGraphQLSchema,
req
);
@@ -139,14 +135,7 @@ const transformers = {
}
return parseACL;
},
relation: async (
targetClass,
field,
value,
originalValue,
parseGraphQLSchema,
{ config, auth, info }
) => {
relation: async (targetClass, field, value, parseGraphQLSchema, { config, auth, info }) => {
if (Object.keys(value).length === 0)
throw new Parse.Error(
Parse.Error.INVALID_POINTER,
@@ -162,10 +151,9 @@ const transformers = {
if (value.createAndAdd) {
nestedObjectsToAdd = (
await Promise.all(
value.createAndAdd.map(async (input, i) => {
value.createAndAdd.map(async input => {
const parseFields = await transformTypes('create', input, {
className: targetClass,
originalFields: originalValue.createAndAdd[i],
parseGraphQLSchema,
req: { config, auth, info },
});
@@ -216,14 +204,7 @@ const transformers = {
}
return op;
},
pointer: async (
targetClass,
field,
value,
originalValue,
parseGraphQLSchema,
{ config, auth, info }
) => {
pointer: async (targetClass, field, value, parseGraphQLSchema, { config, auth, info }) => {
if (Object.keys(value).length > 1 || Object.keys(value).length === 0)
throw new Parse.Error(
Parse.Error.INVALID_POINTER,
@@ -235,7 +216,6 @@ const transformers = {
const parseFields = await transformTypes('create', value.createAndLink, {
className: targetClass,
parseGraphQLSchema,
originalFields: originalValue.createAndLink,
req: { config, auth, info },
});
nestedObjectToAdd = await objectsMutations.createObject(
+3 -3
View File
@@ -1063,19 +1063,19 @@ module.exports.LogLevels = {
triggerAfter: {
env: 'PARSE_SERVER_LOG_LEVELS_TRIGGER_AFTER',
help:
'Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterFind`, `afterLogout`. Default is `info`.',
'Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterSaveFile`, `afterDeleteFile`, `afterFind`, `afterLogout`. Default is `info`.',
default: 'info',
},
triggerBeforeError: {
env: 'PARSE_SERVER_LOG_LEVELS_TRIGGER_BEFORE_ERROR',
help:
'Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on error. Default is `error`.',
'Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on error. Default is `error `.',
default: 'error',
},
triggerBeforeSuccess: {
env: 'PARSE_SERVER_LOG_LEVELS_TRIGGER_BEFORE_SUCCESS',
help:
'Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on success. Default is `info`.',
'Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on success. Default is `info`.',
default: 'info',
},
};
+3 -3
View File
@@ -247,7 +247,7 @@
* @interface LogLevels
* @property {String} cloudFunctionError Log level used by the Cloud Code Functions on error. Default is `error`.
* @property {String} cloudFunctionSuccess Log level used by the Cloud Code Functions on success. Default is `info`.
* @property {String} triggerAfter Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterFind`, `afterLogout`. Default is `info`.
* @property {String} triggerBeforeError Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on error. Default is `error`.
* @property {String} triggerBeforeSuccess Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on success. Default is `info`.
* @property {String} triggerAfter Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterSaveFile`, `afterDeleteFile`, `afterFind`, `afterLogout`. Default is `info`.
* @property {String} triggerBeforeError Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on error. Default is `error `.
* @property {String} triggerBeforeSuccess Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on success. Default is `info`.
*/
+3 -3
View File
@@ -607,15 +607,15 @@ export interface AuthAdapter {
}
export interface LogLevels {
/* Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterFind`, `afterLogout`. Default is `info`.
/* Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterSaveFile`, `afterDeleteFile`, `afterFind`, `afterLogout`. Default is `info`.
:DEFAULT: info
*/
triggerAfter: ?string;
/* Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on success. Default is `info`.
/* Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on success. Default is `info`.
:DEFAULT: info
*/
triggerBeforeSuccess: ?string;
/* Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on error. Default is `error`.
/* Log level used by the Cloud Code Triggers `beforeSave`, `beforeSaveFile`, `beforeDeleteFile`, `beforeFind`, `beforeLogin` on error. Default is `error `.
:DEFAULT: error
*/
triggerBeforeError: ?string;
+24 -20
View File
@@ -804,7 +804,6 @@ RestWrite.prototype._validateEmail = function () {
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
};
return this.config.userController.setEmailVerifyToken(this.data, request, this.storage);
}
@@ -930,25 +929,30 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () {
if (this.auth.user && this.data.authData) {
return;
}
// If sign-up call
if (!this.storage.authProvider) {
// Create request object for verification functions
const { originalObject, updatedObject } = this.buildParseObjects();
const request = {
original: originalObject,
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
};
// Get verification conditions which can be booleans or functions; the purpose of this async/await
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
// conditional statement below, as a developer may decide to execute expensive operations in them
const verifyUserEmails = async () => this.config.verifyUserEmails === true || (typeof this.config.verifyUserEmails === 'function' && await Promise.resolve(this.config.verifyUserEmails(request)) === true);
const preventLoginWithUnverifiedEmail = async () => this.config.preventLoginWithUnverifiedEmail === true || (typeof this.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(this.config.preventLoginWithUnverifiedEmail(request)) === true);
// If verification is required
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail()) {
this.storage.rejectSignup = true;
if (
!this.storage.authProvider && // signup call, with
this.config.preventLoginWithUnverifiedEmail === true && // no login without verification
this.config.verifyUserEmails
) {
// verification is on
this.storage.rejectSignup = true;
return;
}
if (!this.storage.authProvider && this.config.verifyUserEmails) {
let shouldPreventUnverifedLogin = this.config.preventLoginWithUnverifiedEmail;
if (typeof this.config.preventLoginWithUnverifiedEmail === 'function') {
const { originalObject, updatedObject } = this.buildParseObjects();
const request = {
original: originalObject,
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
};
shouldPreventUnverifedLogin = await Promise.resolve(
this.config.preventLoginWithUnverifiedEmail(request)
);
}
if (shouldPreventUnverifedLogin === true) {
return;
}
}
+10 -26
View File
@@ -75,7 +75,7 @@ export class UsersRouter extends ClassesRouter {
) {
payload = req.query;
}
const { username, email, password, ignoreEmailVerification } = payload;
const { username, email, password } = payload;
// TODO: use the right error codes / descriptions.
if (!username && !email) {
@@ -126,7 +126,7 @@ export class UsersRouter extends ClassesRouter {
const accountLockoutPolicy = new AccountLockout(user, req.config);
return accountLockoutPolicy.handleLoginAttempt(isValidPassword);
})
.then(async () => {
.then(() => {
if (!isValidPassword) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
@@ -137,25 +137,12 @@ export class UsersRouter extends ClassesRouter {
if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
// Create request object for verification functions
const request = {
master: req.auth.isMaster,
ip: req.config.ip,
installationId: req.auth.installationId,
object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
};
// If request doesn't use master or maintenance key with ignoring email verification
if (!((req.auth.isMaster || req.auth.isMaintenance) && ignoreEmailVerification)) {
// Get verification conditions which can be booleans or functions; the purpose of this async/await
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
// conditional statement below, as a developer may decide to execute expensive operations in them
const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true);
const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true);
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
}
if (
req.config.verifyUserEmails &&
req.config.preventLoginWithUnverifiedEmail &&
!user.emailVerified
) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
}
this._sanitizeAuthData(user);
@@ -481,7 +468,7 @@ export class UsersRouter extends ClassesRouter {
);
}
const results = await req.config.database.find('_User', { email: email }, {}, Auth.maintenance(req.config));
const results = await req.config.database.find('_User', { email: email });
if (!results.length || results.length < 1) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`);
}
@@ -495,7 +482,7 @@ export class UsersRouter extends ClassesRouter {
}
const userController = req.config.userController;
const send = await userController.regenerateEmailVerifyToken(user, req.auth.isMaster, req.auth.installationId, req.ip);
const send = await userController.regenerateEmailVerifyToken(user, req.auth.isMaster);
if (send) {
userController.sendVerificationEmail(user, req);
}
@@ -663,9 +650,6 @@ export class UsersRouter extends ClassesRouter {
this.route('GET', '/verifyPassword', req => {
return this.handleVerifyPassword(req);
});
this.route('POST', '/verifyPassword', req => {
return this.handleVerifyPassword(req);
});
this.route('POST', '/challenge', req => {
return this.handleChallenge(req);
});
+126 -1
View File
@@ -1,5 +1,6 @@
import { Parse } from 'parse/node';
import * as triggers from '../triggers';
import Deprecator from '../Deprecator/Deprecator';
import { addRateLimit } from '../middlewares';
const Config = require('../Config');
@@ -501,6 +502,130 @@ ParseCloud.afterFind = function (parseClass, handler, validationHandler) {
);
};
/**
* Registers a before save file function.
*
* **Available in Cloud Code only.**
*
* ```
* Parse.Cloud.beforeSaveFile(async (request) => {
* // code here
* }, (request) => {
* // validation code here
* });
*
* Parse.Cloud.beforeSaveFile(async (request) => {
* // code here
* }, { ...validationObject });
*```
*
* @method beforeSaveFile
* @deprecated
* @name Parse.Cloud.beforeSaveFile
* @param {Function} func The function to run before saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
*/
ParseCloud.beforeSaveFile = function (handler, validationHandler) {
Deprecator.logRuntimeDeprecation({
usage: 'Parse.Cloud.beforeSaveFile',
solution: 'Use Parse.Cloud.beforeSave(Parse.File, (request) => {})',
});
ParseCloud.beforeSave(Parse.File, handler, validationHandler);
};
/**
* Registers an after save file function.
*
* **Available in Cloud Code only.**
*
* ```
* Parse.Cloud.afterSaveFile(async (request) => {
* // code here
* }, (request) => {
* // validation code here
* });
*
* Parse.Cloud.afterSaveFile(async (request) => {
* // code here
* }, { ...validationObject });
*```
*
* @method afterSaveFile
* @deprecated
* @name Parse.Cloud.afterSaveFile
* @param {Function} func The function to run after saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
*/
ParseCloud.afterSaveFile = function (handler, validationHandler) {
Deprecator.logRuntimeDeprecation({
usage: 'Parse.Cloud.afterSaveFile',
solution: 'Use Parse.Cloud.afterSave(Parse.File, (request) => {})',
});
ParseCloud.afterSave(Parse.File, handler, validationHandler);
};
/**
* Registers a before delete file function.
*
* **Available in Cloud Code only.**
*
* ```
* Parse.Cloud.beforeDeleteFile(async (request) => {
* // code here
* }, (request) => {
* // validation code here
* });
*
* Parse.Cloud.beforeDeleteFile(async (request) => {
* // code here
* }, { ...validationObject });
*```
*
* @method beforeDeleteFile
* @deprecated
* @name Parse.Cloud.beforeDeleteFile
* @param {Function} func The function to run before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
*/
ParseCloud.beforeDeleteFile = function (handler, validationHandler) {
Deprecator.logRuntimeDeprecation({
usage: 'Parse.Cloud.beforeDeleteFile',
solution: 'Use Parse.Cloud.beforeDelete(Parse.File, (request) => {})',
});
ParseCloud.beforeDelete(Parse.File, handler, validationHandler);
};
/**
* Registers an after delete file function.
*
* **Available in Cloud Code only.**
*
* ```
* Parse.Cloud.afterDeleteFile(async (request) => {
* // code here
* }, (request) => {
* // validation code here
* });
*
* Parse.Cloud.afterDeleteFile(async (request) => {
* // code here
* }, { ...validationObject });
*```
*
* @method afterDeleteFile
* @deprecated
* @name Parse.Cloud.afterDeleteFile
* @param {Function} func The function to after before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
*/
ParseCloud.afterDeleteFile = function (handler, validationHandler) {
Deprecator.logRuntimeDeprecation({
usage: 'Parse.Cloud.afterDeleteFile',
solution: 'Use Parse.Cloud.afterDelete(Parse.File, (request) => {})',
});
ParseCloud.afterDelete(Parse.File, handler, validationHandler);
};
/**
* Registers a before live query server connect function.
*
@@ -678,7 +803,7 @@ module.exports = ParseCloud;
* @property {Integer} contentLength The value from Content-Length header
* @property {String} ip The IP address of the client making the request.
* @property {Object} headers The original HTTP headers for the request.
* @property {String} triggerName The name of the trigger (`beforeSave`, `afterSave`)
* @property {String} triggerName The name of the trigger (`beforeSaveFile`, `afterSaveFile`)
* @property {Object} log The current logger inside Parse Server.
*/
+1 -1
View File
@@ -342,7 +342,7 @@ const handleRateLimit = async (req, res, next) => {
export const handleParseSession = async (req, res, next) => {
try {
const info = req.info;
if (req.auth || req.url === '/sessions/me') {
if (req.auth) {
next();
return;
}
+7 -1
View File
@@ -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;