mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dc0235b5d | ||
|
|
5ae6d6a36d | ||
|
|
3773203a37 | ||
|
|
8ff444d42e | ||
|
|
9cb44c08cc | ||
|
|
09b6a95264 | ||
|
|
422958e246 | ||
|
|
b8535b3db9 | ||
|
|
9282bc595c | ||
|
|
47184f0734 | ||
|
|
d53c1f3668 | ||
|
|
d3ec2e2be7 | ||
|
|
0fa0aabefe | ||
|
|
46761d3ae2 | ||
|
|
bba24dd827 | ||
|
|
30258be121 | ||
|
|
5f9a27fb8e | ||
|
|
297faaece4 | ||
|
|
a6e6549435 | ||
|
|
244e3431cf | ||
|
|
33c648dc81 | ||
|
|
4524c35d51 | ||
|
|
70e0cb3744 | ||
|
|
519dee9b89 | ||
|
|
897acb76a5 | ||
|
|
e5de9daa18 | ||
|
|
223fde0f31 | ||
|
|
d0a5af33ca | ||
|
|
8fe0ae7a2c | ||
|
|
5179501885 | ||
|
|
933e8226df |
@@ -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
@@ -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') }}
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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
@@ -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 | - |
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
[](https://codecov.io/github/parse-community/parse-server?branch=alpha)
|
||||
[](https://github.com/parse-community/parse-dashboard/releases)
|
||||
|
||||
[](https://nodejs.org)
|
||||
[](https://nodejs.org)
|
||||
[](https://www.mongodb.com)
|
||||
[](https://www.postgresql.org)
|
||||
[](https://www.postgresql.org)
|
||||
|
||||
[](https://www.npmjs.com/package/parse-server)
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Generated
+4441
-965
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -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
@@ -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" },
|
||||
]
|
||||
}
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`));
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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_');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -86,6 +86,12 @@ const Category = {
|
||||
};
|
||||
|
||||
function getStore(category, name, applicationId) {
|
||||
const invalidNameRegex = /['"`]/;
|
||||
if (invalidNameRegex.test(name)) {
|
||||
// Prevent a malicious user from injecting properties into the store
|
||||
return {};
|
||||
}
|
||||
|
||||
const path = name.split('.');
|
||||
path.splice(-1); // remove last component
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
@@ -94,7 +100,7 @@ function getStore(category, name, applicationId) {
|
||||
for (const component of path) {
|
||||
store = store[component];
|
||||
if (!store) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return store;
|
||||
|
||||
Reference in New Issue
Block a user