feat: Add support for MongoDB 7 (#8761)

BREAKING CHANGE: `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.
This commit is contained in:
Lucas Coratger
2023-12-10 02:42:40 +01:00
committed by GitHub
parent d3087ed69f
commit 3de8494a22
17 changed files with 667 additions and 2367 deletions

View File

@@ -1,7 +1,7 @@
name: ci name: ci
on: on:
push: 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: pull_request:
branches: branches:
- '**' - '**'
@@ -21,17 +21,17 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript' ] language: ['javascript']
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
source-root: src source-root: src
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v2
check-ci: check-ci:
name: Node Engine Check name: Node Engine Check
timeout-minutes: 15 timeout-minutes: 15
@@ -54,30 +54,30 @@ jobs:
- name: CI Node Engine Check - name: CI Node Engine Check
run: npm run ci:checkNodeEngine run: npm run ci:checkNodeEngine
check-lint: check-lint:
name: Lint name: Lint
timeout-minutes: 15 timeout-minutes: 15
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }} - name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules - name: Cache Node.js modules
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- run: npm run lint - run: npm run lint
check-definitions: check-definitions:
name: Check Definitions name: Check Definitions
timeout-minutes: 5 timeout-minutes: 5
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }} - name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
@@ -95,25 +95,25 @@ jobs:
- name: CI Definitions Check - name: CI Definitions Check
run: npm run ci:definitionsCheck run: npm run ci:definitionsCheck
check-circular: check-circular:
name: Circular Dependencies name: Circular Dependencies
timeout-minutes: 5 timeout-minutes: 5
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }} - name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules - name: Cache Node.js modules
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- run: npm run madge:circular - run: npm run madge:circular
check-docker: check-docker:
name: Docker Build name: Docker Build
timeout-minutes: 15 timeout-minutes: 15
@@ -147,19 +147,23 @@ jobs:
include: include:
- name: MongoDB 4.2, ReplicaSet - name: MongoDB 4.2, ReplicaSet
MONGODB_VERSION: 4.2.19 MONGODB_VERSION: 4.2.19
MONGODB_TOPOLOGY: replicaset MONGODB_TOPOLOGY: replset
NODE_VERSION: 19.3.0 NODE_VERSION: 19.3.0
- name: MongoDB 4.4, ReplicaSet - name: MongoDB 4.4, ReplicaSet
MONGODB_VERSION: 4.4.13 MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: replicaset MONGODB_TOPOLOGY: replset
NODE_VERSION: 19.3.0 NODE_VERSION: 19.3.0
- name: MongoDB 5, ReplicaSet - name: MongoDB 5, ReplicaSet
MONGODB_VERSION: 5.3.2 MONGODB_VERSION: 5.3.2
MONGODB_TOPOLOGY: replicaset MONGODB_TOPOLOGY: replset
NODE_VERSION: 19.3.0 NODE_VERSION: 19.3.0
- name: MongoDB 6, ReplicaSet - name: MongoDB 6, ReplicaSet
MONGODB_VERSION: 6.0.2 MONGODB_VERSION: 6.0.2
MONGODB_TOPOLOGY: replicaset MONGODB_TOPOLOGY: replset
NODE_VERSION: 19.3.0
- name: MongoDB 7, ReplicaSet
MONGODB_VERSION: 7.0.1
MONGODB_TOPOLOGY: replset
NODE_VERSION: 19.3.0 NODE_VERSION: 19.3.0
- name: Redis Cache - name: Redis Cache
PARSE_SERVER_TEST_CACHE: redis PARSE_SERVER_TEST_CACHE: redis
@@ -186,7 +190,7 @@ jobs:
redis: redis:
image: redis image: redis
ports: ports:
- 6379:6379 - 6379:6379
env: env:
MONGODB_VERSION: ${{ matrix.MONGODB_VERSION }} MONGODB_VERSION: ${{ matrix.MONGODB_VERSION }}
MONGODB_TOPOLOGY: ${{ matrix.MONGODB_TOPOLOGY }} MONGODB_TOPOLOGY: ${{ matrix.MONGODB_TOPOLOGY }}

View File

@@ -139,12 +139,13 @@ Parse Server is continuously tested with the most recent releases of Node.js to
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. 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 | | Version | Latest Version | End-of-Life | Compatible |
|-------------|----------------|---------------|------------| | ----------- | -------------- | ------------- | ---------- |
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes | | MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes | | MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes | | MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes | | MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes | | MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
| MongoDB 7 | 7.0.1 | TDB | ✅ Yes |
#### PostgreSQL #### PostgreSQL

2594
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,7 @@
"lodash": "4.17.21", "lodash": "4.17.21",
"lru-cache": "9.1.1", "lru-cache": "9.1.1",
"mime": "3.0.0", "mime": "3.0.0",
"mongodb": "4.10.0", "mongodb": "5.9.0",
"mustache": "4.2.0", "mustache": "4.2.0",
"otpauth": "9.1.2", "otpauth": "9.1.2",
"parse": "4.1.0", "parse": "4.1.0",
@@ -98,7 +98,7 @@
"madge": "5.0.1", "madge": "5.0.1",
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter", "mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
"mongodb-runner": "4.8.1", "mongodb-runner": "5.4.4",
"mongodb-version-list": "1.0.0", "mongodb-version-list": "1.0.0",
"node-abort-controller": "3.0.1", "node-abort-controller": "3.0.1",
"node-fetch": "3.2.10", "node-fetch": "3.2.10",
@@ -117,18 +117,18 @@
"lint-fix": "eslint --fix --cache ./", "lint-fix": "eslint --fix --cache ./",
"build": "babel src/ -d lib/ --copy-files", "build": "babel src/ -d lib/ --copy-files",
"watch": "babel --watch 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", "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:testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "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": "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.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: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: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:6.0.2": "npm run test:mongodb --dbversion=6.0.2",
"posttest:mongodb": "mongodb-runner stop", "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", "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",
"testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine",
"test": "npm run testonly", "test": "npm run testonly",
"posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner stop", "posttest": "cross-env mongodb-runner stop --all",
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 nyc jasmine", "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 nyc jasmine",
"start": "node ./bin/parse-server", "start": "node ./bin/parse-server",
"prettier": "prettier --write {src,spec}/{**/*,*}.js", "prettier": "prettier --write {src,spec}/{**/*,*}.js",

View File

@@ -339,11 +339,12 @@ describe('AudiencesRouter', () => {
) )
.then(result => { .then(result => {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
database database
.collection('test__Audience') .collection('test__Audience')
.find({ _id: audience.objectId }) .find({ _id: audience.objectId })
.toArray((error, rows) => { .toArray()
expect(error).toEqual(undefined); .then(rows => {
expect(rows[0]['times_used']).toEqual(1); expect(rows[0]['times_used']).toEqual(1);
expect(rows[0]['_last_used']).toEqual(now); expect(rows[0]['_last_used']).toEqual(now);
Parse._request( Parse._request(
@@ -362,6 +363,9 @@ describe('AudiencesRouter', () => {
.catch(error => { .catch(error => {
done.fail(error); done.fail(error);
}); });
})
.catch(error => {
done.fail(error);
}); });
}); });
}); });

View File

@@ -1454,7 +1454,6 @@ describe('oauth2 auth adapter', () => {
describe('apple signin auth adapter', () => { describe('apple signin auth adapter', () => {
const apple = require('../lib/Adapters/Auth/apple'); const apple = require('../lib/Adapters/Auth/apple');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const util = require('util');
const authUtils = require('../lib/Adapters/Auth/utils'); const authUtils = require('../lib/Adapters/Auth/utils');
it('(using client id as string) should throw error with missing id_token', async () => { it('(using client id as string) should throw error with missing id_token', async () => {
@@ -1512,12 +1511,10 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; 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, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); 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( const result = await apple.validateAuthData(
{ id: 'the_user_id', token: 'the_token' }, { id: 'the_user_id', token: 'the_token' },
@@ -1529,11 +1526,9 @@ describe('apple signin auth adapter', () => {
it('should not verify invalid id_token', async () => { it('should not verify invalid id_token', async () => {
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
try { try {
await apple.validateAuthData( await apple.validateAuthData(
@@ -1566,11 +1561,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData( const result = await apple.validateAuthData(
@@ -1588,11 +1581,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData( const result = await apple.validateAuthData(
@@ -1610,11 +1601,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await apple.validateAuthData( const result = await apple.validateAuthData(
@@ -1630,11 +1619,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -1658,11 +1645,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -1687,11 +1672,9 @@ describe('apple signin auth adapter', () => {
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -1759,11 +1742,9 @@ describe('apple signin auth adapter', () => {
sub: 'a_different_user_id', sub: 'a_different_user_id',
}; };
const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -2025,7 +2006,6 @@ describe('microsoft graph auth adapter', () => {
describe('facebook limited auth adapter', () => { describe('facebook limited auth adapter', () => {
const facebook = require('../lib/Adapters/Auth/facebook'); const facebook = require('../lib/Adapters/Auth/facebook');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const util = require('util');
const authUtils = require('../lib/Adapters/Auth/utils'); const authUtils = require('../lib/Adapters/Auth/utils');
// TODO: figure out a way to run this test alongside facebook classic tests // TODO: figure out a way to run this test alongside facebook classic tests
@@ -2086,18 +2066,11 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(), exp: Date.now(),
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken.header);
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); 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( const result = await facebook.validateAuthData(
{ id: 'the_user_id', token: 'the_token' }, { id: 'the_user_id', token: 'the_token' },
@@ -2108,17 +2081,10 @@ describe('facebook limited auth adapter', () => {
}); });
it('should not verify invalid id_token', async () => { it('should not verify invalid id_token', async () => {
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
try { try {
await facebook.validateAuthData( await facebook.validateAuthData(
@@ -2150,17 +2116,10 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(), exp: Date.now(),
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData( const result = await facebook.validateAuthData(
@@ -2177,17 +2136,10 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(), exp: Date.now(),
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData( const result = await facebook.validateAuthData(
@@ -2204,17 +2156,10 @@ describe('facebook limited auth adapter', () => {
exp: Date.now(), exp: Date.now(),
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
const result = await facebook.validateAuthData( const result = await facebook.validateAuthData(
@@ -2229,17 +2174,10 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com', iss: 'https://not.facebook.com',
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -2262,17 +2200,10 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com', iss: 'https://not.facebook.com',
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -2296,17 +2227,10 @@ describe('facebook limited auth adapter', () => {
iss: 'https://not.facebook.com', iss: 'https://not.facebook.com',
sub: 'the_user_id', sub: 'the_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {
@@ -2382,17 +2306,10 @@ describe('facebook limited auth adapter', () => {
aud: 'invalid_client_id', aud: 'invalid_client_id',
sub: 'a_different_user_id', sub: 'a_different_user_id',
}; };
const fakeDecodedToken = { const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } };
header: { kid: '123', alg: 'RS256' }, const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
};
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken); spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
const fakeGetSigningKeyAsyncFunction = () => { spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
return {
kid: '123',
rsaPublicKey: 'the_rsa_public_key',
};
};
spyOn(util, 'promisify').and.callFake(() => fakeGetSigningKeyAsyncFunction);
spyOn(jwt, 'verify').and.callFake(() => fakeClaim); spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
try { try {

View File

@@ -284,25 +284,11 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
amount: 1, amount: 1,
}, },
}; };
await Parse.Server.database.update( await Parse.Server.database.update('MyClass', query, update, { upsert: true });
'MyClass',
query,
update,
{ upsert: true },
);
update.objectId.amount = uuid2; update.objectId.amount = uuid2;
await Parse.Server.database.update( await Parse.Server.database.update('MyClass', query, update, { upsert: true });
'MyClass',
query,
update,
{ upsert: true },
);
const res = await Parse.Server.database.find( const res = await Parse.Server.database.find(schema.className, {}, {});
schema.className,
{},
{},
);
expect(res.length).toBe(1); expect(res.length).toBe(1);
expect(res[0].objectId).toBe(uuid1); expect(res[0].objectId).toBe(uuid1);
expect(res[0].count).toBe(2); expect(res[0].count).toBe(2);

View File

@@ -86,6 +86,7 @@ describe_only_db('mongo')('Parse.Query hint', () => {
let result = await collection.aggregate([{ $group: { _id: '$foo' } }], { let result = await collection.aggregate([{ $group: { _id: '$foo' } }], {
explain: true, explain: true,
}); });
let { queryPlanner } = result[0].stages[0].$cursor; let { queryPlanner } = result[0].stages[0].$cursor;
expect(queryPlanner.winningPlan.stage).toBe('COLLSCAN'); expect(queryPlanner.winningPlan.stage).toBe('COLLSCAN');
@@ -93,6 +94,7 @@ describe_only_db('mongo')('Parse.Query hint', () => {
hint: '_id_', hint: '_id_',
explain: true, explain: true,
}); });
queryPlanner = result[0].stages[0].$cursor.queryPlanner; queryPlanner = result[0].stages[0].$cursor.queryPlanner;
expect(queryPlanner.winningPlan.stage).toBe('FETCH'); expect(queryPlanner.winningPlan.stage).toBe('FETCH');
expect(queryPlanner.winningPlan.inputStage.indexName).toBe('_id_'); expect(queryPlanner.winningPlan.inputStage.indexName).toBe('_id_');

View File

@@ -251,8 +251,8 @@ afterEach(function (done) {
}) })
.then(() => Parse.User.logOut()) .then(() => Parse.User.logOut())
.then( .then(
() => { }, () => {},
() => { } () => {}
) // swallow errors ) // swallow errors
.then(() => { .then(() => {
// Connection close events are not immediate on node 10+... wait a bit // Connection close events are not immediate on node 10+... wait a bit

View File

@@ -143,10 +143,10 @@ describe('rest create', () => {
const res = await request({ const res = await request({
headers: { headers: {
'X-Parse-Application-Id': 'test', 'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest' 'X-Parse-REST-API-Key': 'rest',
}, },
method: 'GET', method: 'GET',
url: `http://localhost:8378/1/classes/TestObject/${id}` url: `http://localhost:8378/1/classes/TestObject/${id}`,
}); });
return res.data; return res.data;
@@ -158,10 +158,10 @@ describe('rest create', () => {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test', 'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest', 'X-Parse-REST-API-Key': 'rest',
'X-Parse-Maintenance-Key': 'testing' 'X-Parse-Maintenance-Key': 'testing',
}, },
method: 'POST', method: 'POST',
url: 'http://localhost:8378/1/classes/TestObject' url: 'http://localhost:8378/1/classes/TestObject',
}; };
}); });
@@ -206,8 +206,7 @@ describe('rest create', () => {
try { try {
await request(req); await request(req);
fail(); fail();
} } catch (err) {
catch (err) {
expect(err.data.code).toEqual(Parse.Error.VALIDATION_ERROR); expect(err.data.code).toEqual(Parse.Error.VALIDATION_ERROR);
} }
}); });
@@ -230,8 +229,7 @@ describe('rest create', () => {
try { try {
await request(req); await request(req);
fail(); fail();
} } catch (err) {
catch (err) {
expect(err.data.code).toEqual(Parse.Error.INCORRECT_TYPE); expect(err.data.code).toEqual(Parse.Error.INCORRECT_TYPE);
} }
}); });

View File

@@ -235,32 +235,6 @@ describe('Vulnerabilities', () => {
await new Promise(resolve => server.close(resolve)); 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 () => { it('denies write request with custom denylist of key/value', async () => {
await reconfigureServer({ await reconfigureServer({
requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }], requestKeywordDenylist: [{ key: 'a[K]ey', value: 'aValue[123]*' }],

View File

@@ -3,7 +3,6 @@
const Parse = require('parse/node').Parse; const Parse = require('parse/node').Parse;
const jwksClient = require('jwks-rsa'); const jwksClient = require('jwks-rsa');
const util = require('util');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const authUtils = require('./utils'); const authUtils = require('./utils');
@@ -17,11 +16,9 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
cacheMaxAge, cacheMaxAge,
}); });
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
let key; let key;
try { try {
key = await asyncGetSigningKeyFunction(keyId); key = await authUtils.getSigningKey(client, keyId);
} catch (error) { } catch (error) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND, Parse.Error.OBJECT_NOT_FOUND,

View File

@@ -2,7 +2,6 @@
const Parse = require('parse/node').Parse; const Parse = require('parse/node').Parse;
const crypto = require('crypto'); const crypto = require('crypto');
const jwksClient = require('jwks-rsa'); const jwksClient = require('jwks-rsa');
const util = require('util');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const httpsRequest = require('./httpsRequest'); const httpsRequest = require('./httpsRequest');
const authUtils = require('./utils'); const authUtils = require('./utils');
@@ -60,11 +59,9 @@ const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
cacheMaxAge, cacheMaxAge,
}); });
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
let key; let key;
try { try {
key = await asyncGetSigningKeyFunction(keyId); key = await authUtils.getSigningKey(client, keyId);
} catch (error) { } catch (error) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND, Parse.Error.OBJECT_NOT_FOUND,

View File

@@ -1,4 +1,5 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const util = require('util');
const Parse = require('parse/node').Parse; const Parse = require('parse/node').Parse;
const getHeaderFromToken = token => { const getHeaderFromToken = token => {
const decodedToken = jwt.decode(token, { complete: true }); const decodedToken = jwt.decode(token, { complete: true });
@@ -8,6 +9,16 @@ const getHeaderFromToken = token => {
return decodedToken.header; 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 = { module.exports = {
getHeaderFromToken, getHeaderFromToken,
getSigningKey,
}; };

View File

@@ -160,18 +160,10 @@ export default class MongoCollection {
} }
_ensureSparseUniqueIndexInBackground(indexRequest) { _ensureSparseUniqueIndexInBackground(indexRequest) {
return new Promise((resolve, reject) => { return this._mongoCollection.createIndex(indexRequest, {
this._mongoCollection.createIndex( unique: true,
indexRequest, background: true,
{ unique: true, background: true, sparse: true }, sparse: true,
error => {
if (error) {
reject(error);
} else {
resolve();
}
}
);
}); });
} }

View File

@@ -172,7 +172,6 @@ export class MongoStorageAdapter implements StorageAdapter {
// parsing and re-formatting causes the auth value (if there) to get URI // parsing and re-formatting causes the auth value (if there) to get URI
// encoded // encoded
const encodedUri = formatUrl(parseUrl(this._uri)); const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions) this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions)
.then(client => { .then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client // Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
@@ -687,13 +686,8 @@ export class MongoStorageAdapter implements StorageAdapter {
}; };
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then( .then(collection =>
collection => collection._mongoCollection.createIndex(indexCreationRequest, indexOptions)
new Promise((resolve, reject) =>
collection._mongoCollection.createIndex(indexCreationRequest, indexOptions, error =>
error ? reject(error) : resolve()
)
)
) )
.catch(err => this.handleError(err)); .catch(err => this.handleError(err));
} }

View File

@@ -457,6 +457,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) =>
); );
} }
value = mapValues(restValue, transformInteriorValue); value = mapValues(restValue, transformInteriorValue);
return { key: restKey, value }; return { key: restKey, value };
}; };