From 45301a67e8b265693b37759d9e2622470df32a8a Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 18 Jun 2023 03:19:07 +0200 Subject: [PATCH 01/16] docs: Fix broken logo link in API docs (#8642) --- jsdoc-conf.json | 2 +- package-lock.json | 7 ++++++- release_docs.sh | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/jsdoc-conf.json b/jsdoc-conf.json index 4a1e5de846..efbaa0a37c 100644 --- a/jsdoc-conf.json +++ b/jsdoc-conf.json @@ -29,7 +29,7 @@ "template": "./node_modules/clean-jsdoc-theme", "theme_opts": { "default_theme": "dark", - "title": "Parse Server", + "title": "", "create_style": "header, .sidebar-section-title, .sidebar-title { color: #139cee !important } .logo { margin-left : 40px; margin-right: 40px }" } }, diff --git a/package-lock.json b/package-lock.json index 20ad3e754d..c6130f0e07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.6", - "semver": "^7.5.1", + "semver": "7.5.1", "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.0", @@ -15213,6 +15213,11 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, "engines": { "node": ">=0.10.0" } diff --git a/release_docs.sh b/release_docs.sh index a7bb26324c..a9cc5bf3bc 100755 --- a/release_docs.sh +++ b/release_docs.sh @@ -27,3 +27,8 @@ npm run docs mkdir -p "docs/api/${DEST}" cp -R out/* "docs/api/${DEST}" + +# Copy other resources +RESOURCE_DIR=".github" +mkdir -p "docs/${RESOURCE_DIR}" +cp "./.github/parse-server-logo.png" "docs/${RESOURCE_DIR}/" From 9674d4a2c0a9d0cda112056a6a2b1629931f37a3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 18 Jun 2023 01:20:54 +0000 Subject: [PATCH 02/16] chore(release): 6.3.0-alpha.1 [skip ci] # [6.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.2.0...6.3.0-alpha.1) (2023-06-18) ### Bug Fixes * Cloud Code Trigger `afterSave` executes even if not set ([#8520](https://github.com/parse-community/parse-server/issues/8520)) ([afd0515](https://github.com/parse-community/parse-server/commit/afd0515e207bd947840579d3f245980dffa6f804)) * GridFS file storage doesn't work with certain `enableSchemaHooks` settings ([#8467](https://github.com/parse-community/parse-server/issues/8467)) ([d4cda4b](https://github.com/parse-community/parse-server/commit/d4cda4b26c9bde8c812549b8780bea1cfabdb394)) * Inaccurate table total row count for PostgreSQL ([#8511](https://github.com/parse-community/parse-server/issues/8511)) ([0823a02](https://github.com/parse-community/parse-server/commit/0823a02fbf80bc88dc403bc47e9f5c6597ea78b4)) * LiveQuery server is not shut down properly when `handleShutdown` is called ([#8491](https://github.com/parse-community/parse-server/issues/8491)) ([967700b](https://github.com/parse-community/parse-server/commit/967700bdbc94c74f75ba84d2b3f4b9f3fd2dca0b)) * Rate limit feature is incompatible with Node 14 ([#8578](https://github.com/parse-community/parse-server/issues/8578)) ([f911f2c](https://github.com/parse-community/parse-server/commit/f911f2cd3a8c45cd326272dcd681532764a3761e)) * Unnecessary log entries by `extendSessionOnUse` ([#8562](https://github.com/parse-community/parse-server/issues/8562)) ([fd6a007](https://github.com/parse-community/parse-server/commit/fd6a0077f2e5cf83d65e52172ae5a950ab0f1eae)) ### Features * `extendSessionOnUse` to automatically renew Parse Sessions ([#8505](https://github.com/parse-community/parse-server/issues/8505)) ([6f885d3](https://github.com/parse-community/parse-server/commit/6f885d36b94902fdfea873fc554dee83589e6029)) * Add new Parse Server option `preventSignupWithUnverifiedEmail` to prevent returning a user without session token on sign-up with unverified email address ([#8451](https://github.com/parse-community/parse-server/issues/8451)) ([82da308](https://github.com/parse-community/parse-server/commit/82da30842a55980aa90cb7680fbf6db37ee16dab)) * Add option to change the log level of logs emitted by Cloud Functions ([#8530](https://github.com/parse-community/parse-server/issues/8530)) ([2caea31](https://github.com/parse-community/parse-server/commit/2caea310be412d82b04a85716bc769ccc410316d)) * Add support for `$eq` query constraint in LiveQuery ([#8614](https://github.com/parse-community/parse-server/issues/8614)) ([656d673](https://github.com/parse-community/parse-server/commit/656d673cf5dea354e4f2b3d4dc2b29a41d311b3e)) * Add zones for rate limiting by `ip`, `user`, `session`, `global` ([#8508](https://github.com/parse-community/parse-server/issues/8508)) ([03fba97](https://github.com/parse-community/parse-server/commit/03fba97e0549bfcaeee9f2fa4c9905dbcc91840e)) * Allow `Parse.Object` pointers in Cloud Code arguments ([#8490](https://github.com/parse-community/parse-server/issues/8490)) ([28aeda3](https://github.com/parse-community/parse-server/commit/28aeda3f160efcbbcf85a85484a8d26567fa9761)) ### Reverts * fix: Inaccurate table total row count for PostgreSQL ([6722110](https://github.com/parse-community/parse-server/commit/6722110f203bc5fdcaa68cdf091cf9e7b48d1cff)) --- changelogs/CHANGELOG_alpha.md | 25 +++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index b9b1925b78..c7a229e5a7 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,28 @@ +# [6.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.2.0...6.3.0-alpha.1) (2023-06-18) + + +### Bug Fixes + +* Cloud Code Trigger `afterSave` executes even if not set ([#8520](https://github.com/parse-community/parse-server/issues/8520)) ([afd0515](https://github.com/parse-community/parse-server/commit/afd0515e207bd947840579d3f245980dffa6f804)) +* GridFS file storage doesn't work with certain `enableSchemaHooks` settings ([#8467](https://github.com/parse-community/parse-server/issues/8467)) ([d4cda4b](https://github.com/parse-community/parse-server/commit/d4cda4b26c9bde8c812549b8780bea1cfabdb394)) +* Inaccurate table total row count for PostgreSQL ([#8511](https://github.com/parse-community/parse-server/issues/8511)) ([0823a02](https://github.com/parse-community/parse-server/commit/0823a02fbf80bc88dc403bc47e9f5c6597ea78b4)) +* LiveQuery server is not shut down properly when `handleShutdown` is called ([#8491](https://github.com/parse-community/parse-server/issues/8491)) ([967700b](https://github.com/parse-community/parse-server/commit/967700bdbc94c74f75ba84d2b3f4b9f3fd2dca0b)) +* Rate limit feature is incompatible with Node 14 ([#8578](https://github.com/parse-community/parse-server/issues/8578)) ([f911f2c](https://github.com/parse-community/parse-server/commit/f911f2cd3a8c45cd326272dcd681532764a3761e)) +* Unnecessary log entries by `extendSessionOnUse` ([#8562](https://github.com/parse-community/parse-server/issues/8562)) ([fd6a007](https://github.com/parse-community/parse-server/commit/fd6a0077f2e5cf83d65e52172ae5a950ab0f1eae)) + +### Features + +* `extendSessionOnUse` to automatically renew Parse Sessions ([#8505](https://github.com/parse-community/parse-server/issues/8505)) ([6f885d3](https://github.com/parse-community/parse-server/commit/6f885d36b94902fdfea873fc554dee83589e6029)) +* Add new Parse Server option `preventSignupWithUnverifiedEmail` to prevent returning a user without session token on sign-up with unverified email address ([#8451](https://github.com/parse-community/parse-server/issues/8451)) ([82da308](https://github.com/parse-community/parse-server/commit/82da30842a55980aa90cb7680fbf6db37ee16dab)) +* Add option to change the log level of logs emitted by Cloud Functions ([#8530](https://github.com/parse-community/parse-server/issues/8530)) ([2caea31](https://github.com/parse-community/parse-server/commit/2caea310be412d82b04a85716bc769ccc410316d)) +* Add support for `$eq` query constraint in LiveQuery ([#8614](https://github.com/parse-community/parse-server/issues/8614)) ([656d673](https://github.com/parse-community/parse-server/commit/656d673cf5dea354e4f2b3d4dc2b29a41d311b3e)) +* Add zones for rate limiting by `ip`, `user`, `session`, `global` ([#8508](https://github.com/parse-community/parse-server/issues/8508)) ([03fba97](https://github.com/parse-community/parse-server/commit/03fba97e0549bfcaeee9f2fa4c9905dbcc91840e)) +* Allow `Parse.Object` pointers in Cloud Code arguments ([#8490](https://github.com/parse-community/parse-server/issues/8490)) ([28aeda3](https://github.com/parse-community/parse-server/commit/28aeda3f160efcbbcf85a85484a8d26567fa9761)) + +### Reverts + +* fix: Inaccurate table total row count for PostgreSQL ([6722110](https://github.com/parse-community/parse-server/commit/6722110f203bc5fdcaa68cdf091cf9e7b48d1cff)) + # [6.1.0-alpha.20](https://github.com/parse-community/parse-server/compare/6.1.0-alpha.19...6.1.0-alpha.20) (2023-06-09) diff --git a/package-lock.json b/package-lock.json index c6130f0e07..146ac05e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-server", - "version": "6.3.0-beta.1", + "version": "6.3.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "6.3.0-beta.1", + "version": "6.3.0-alpha.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index e0b943eab4..7a58f71970 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "6.3.0-beta.1", + "version": "6.3.0-alpha.1", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From ccd8763161fa49c164a88a3190c41d1c24abd6c3 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 19 Jun 2023 15:27:52 +1000 Subject: [PATCH 03/16] refactor: Add option to convert Parse.Pointer in Cloud Function payload --- spec/CloudCode.spec.js | 20 ++++++++++++++++++++ src/Options/Definitions.js | 6 ++++++ src/Options/docs.js | 1 + src/Options/index.js | 3 +++ src/Routers/FunctionsRouter.js | 14 +++++++------- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index a8795a4e84..1a993fd3c3 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1353,7 +1353,27 @@ describe('Cloud Code', () => { }); }); + it('should not enode Parse Objects', async () => { + const user = new Parse.User(); + user.setUsername('username'); + user.setPassword('password'); + user.set('deleted', false); + await user.signUp(); + Parse.Cloud.define( + 'deleteAccount', + async req => { + expect(req.params.object instanceof Parse.Object).not.toBeTrue(); + return 'Object deleted'; + }, + { + requireMaster: true, + } + ); + await Parse.Cloud.run('deleteAccount', { object: user.toPointer() }, { useMasterKey: true }); + }); + it('allow cloud to encode Parse Objects', async () => { + await reconfigureServer({ encodeCloudPointers: true }); const user = new Parse.User(); user.setUsername('username'); user.setPassword('password'); diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 3815902c51..8cf33667f1 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -210,6 +210,12 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, + encodeCloudPointers: { + env: 'PARSE_SERVER_ENCODE_CLOUD_POINTERS', + help: 'Whether Parse Pointers should be encoded in Cloud Code.', + action: parsers.booleanParser, + default: false, + }, encryptionKey: { env: 'PARSE_SERVER_ENCRYPTION_KEY', help: 'Key for encrypting your files', diff --git a/src/Options/docs.js b/src/Options/docs.js index 847e7df944..8c447368a2 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -40,6 +40,7 @@ * @property {Number} emailVerifyTokenValidityDuration Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to `undefined`, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is `undefined`.
Requires option `verifyUserEmails: true`. * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors + * @property {Boolean} encodeCloudPointers Whether Parse Pointers should be encoded in Cloud Code. * @property {String} encryptionKey Key for encrypting your files * @property {Boolean} enforcePrivateUsers Set to true if new users should be created without public read and write access. * @property {Boolean} expireInactiveSessions Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date. diff --git a/src/Options/index.js b/src/Options/index.js index 87813147f7..e339b5978d 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -196,6 +196,9 @@ export interface ParseServerOptions { cacheAdapter: ?Adapter; /* Adapter module for email sending */ emailAdapter: ?Adapter; + /* Whether Parse Pointers should be encoded in Cloud Code. + :DEFAULT: false */ + encodeCloudPointers: ?boolean; /* Public URL to your parse server with http:// or https://. :ENV: PARSE_PUBLIC_SERVER_URL */ publicServerURL: ?string; diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index da69d54e0c..de61893c03 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -9,7 +9,7 @@ import { jobStatusHandler } from '../StatusHandler'; import _ from 'lodash'; import { logger } from '../logger'; -function parseObject(obj) { +function parseObject(obj, config) { if (Array.isArray(obj)) { return obj.map(item => { return parseObject(item); @@ -18,21 +18,21 @@ function parseObject(obj) { return Object.assign(new Date(obj.iso), obj); } else if (obj && obj.__type == 'File') { return Parse.File.fromJSON(obj); - } else if (obj && obj.__type == 'Pointer') { + } else if (obj && obj.__type == 'Pointer' && config.encodeCloudPointers) { return Parse.Object.fromJSON({ __type: 'Pointer', className: obj.className, objectId: obj.objectId, }); } else if (obj && typeof obj === 'object') { - return parseParams(obj); + return parseParams(obj, config); } else { return obj; } } -function parseParams(params) { - return _.mapValues(params, parseObject); +function parseParams(params, config) { + return _.mapValues(params, item => parseObject(item, config)); } export class FunctionsRouter extends PromiseRouter { @@ -66,7 +66,7 @@ export class FunctionsRouter extends PromiseRouter { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid job.'); } let params = Object.assign({}, req.body, req.query); - params = parseParams(params); + params = parseParams(params, req.config); const request = { params: params, log: req.config.loggerController, @@ -126,7 +126,7 @@ export class FunctionsRouter extends PromiseRouter { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`); } let params = Object.assign({}, req.body, req.query); - params = parseParams(params); + params = parseParams(params, req.config); const request = { params: params, master: req.auth && req.auth.isMaster, From 2333a59f683bf47f6c2503f8628f6cef65d48076 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 19 Jun 2023 22:38:14 +0200 Subject: [PATCH 04/16] Update spec/CloudCode.spec.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- spec/CloudCode.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 1a993fd3c3..939f373924 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1353,7 +1353,7 @@ describe('Cloud Code', () => { }); }); - it('should not enode Parse Objects', async () => { + it('should not encode Parse Objects', async () => { const user = new Parse.User(); user.setUsername('username'); user.setPassword('password'); From 3710da737909f7877a00ad431ad942084ecb808c Mon Sep 17 00:00:00 2001 From: Corey Date: Tue, 20 Jun 2023 06:07:10 -0400 Subject: [PATCH 05/16] refactor: Replace deprecated `substr` with `substring` (#8644) --- postinstall.js | 2 +- src/Adapters/Files/GridFSBucketAdapter.js | 71 ++++++------------- .../Postgres/PostgresStorageAdapter.js | 14 ++-- src/Config.js | 2 +- 4 files changed, 32 insertions(+), 57 deletions(-) diff --git a/postinstall.js b/postinstall.js index fef1fb31ff..fe1fc96bae 100644 --- a/postinstall.js +++ b/postinstall.js @@ -1,6 +1,6 @@ const pkg = require('./package.json'); -const version = parseFloat(process.version.substr(1)); +const version = parseFloat(process.version.substring(1)); const minimum = parseFloat(pkg.engines.node.match(/\d+/g).join('.')); module.exports = function () { diff --git a/src/Adapters/Files/GridFSBucketAdapter.js b/src/Adapters/Files/GridFSBucketAdapter.js index 451165789d..76a8f25d1b 100644 --- a/src/Adapters/Files/GridFSBucketAdapter.js +++ b/src/Adapters/Files/GridFSBucketAdapter.js @@ -28,7 +28,11 @@ export class GridFSBucketAdapter extends FilesAdapter { this._algorithm = 'aes-256-gcm'; this._encryptionKey = encryptionKey !== undefined - ? crypto.createHash('sha256').update(String(encryptionKey)).digest('base64').substr(0, 32) + ? crypto + .createHash('sha256') + .update(String(encryptionKey)) + .digest('base64') + .substring(0, 32) : null; const defaultMongoOptions = { useNewUrlParser: true, @@ -138,8 +142,8 @@ export class GridFSBucketAdapter extends FilesAdapter { } async rotateEncryptionKey(options = {}) { - var fileNames = []; - var oldKeyFileAdapter = {}; + let fileNames = []; + let oldKeyFileAdapter = {}; const bucket = await this._getBucket(); if (options.oldKey !== undefined) { oldKeyFileAdapter = new GridFSBucketAdapter( @@ -158,51 +162,22 @@ export class GridFSBucketAdapter extends FilesAdapter { fileNames.push(file.filename); }); } - return new Promise(resolve => { - var fileNamesNotRotated = fileNames; - var fileNamesRotated = []; - var fileNameTotal = fileNames.length; - var fileNameIndex = 0; - fileNames.forEach(fileName => { - oldKeyFileAdapter - .getFileData(fileName) - .then(plainTextData => { - //Overwrite file with data encrypted with new key - this.createFile(fileName, plainTextData) - .then(() => { - fileNamesRotated.push(fileName); - fileNamesNotRotated = fileNamesNotRotated.filter(function (value) { - return value !== fileName; - }); - fileNameIndex += 1; - if (fileNameIndex == fileNameTotal) { - resolve({ - rotated: fileNamesRotated, - notRotated: fileNamesNotRotated, - }); - } - }) - .catch(() => { - fileNameIndex += 1; - if (fileNameIndex == fileNameTotal) { - resolve({ - rotated: fileNamesRotated, - notRotated: fileNamesNotRotated, - }); - } - }); - }) - .catch(() => { - fileNameIndex += 1; - if (fileNameIndex == fileNameTotal) { - resolve({ - rotated: fileNamesRotated, - notRotated: fileNamesNotRotated, - }); - } - }); - }); - }); + let fileNamesNotRotated = fileNames; + const fileNamesRotated = []; + for (const fileName of fileNames) { + try { + const plainTextData = await oldKeyFileAdapter.getFileData(fileName); + // Overwrite file with data encrypted with new key + await this.createFile(fileName, plainTextData); + fileNamesRotated.push(fileName); + fileNamesNotRotated = fileNamesNotRotated.filter(function (value) { + return value !== fileName; + }); + } catch (err) { + continue; + } + } + return { rotated: fileNamesRotated, notRotated: fileNamesNotRotated }; } getFileLocation(config, filename) { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 3e8e867799..3ad59ec77f 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -231,7 +231,7 @@ const transformAggregateField = fieldName => { if (fieldName === '$_updated_at') { return 'updatedAt'; } - return fieldName.substr(1); + return fieldName.substring(1); }; const validateKeys = object => { @@ -1921,14 +1921,14 @@ export class PostgresStorageAdapter implements StorageAdapter { }; } if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') { - let coords = object[fieldName]; - coords = coords.substr(2, coords.length - 4).split('),('); - coords = coords.map(point => { + let coords = new String(object[fieldName]); + coords = coords.substring(2, coords.length - 2).split('),('); + const updatedCoords = coords.map(point => { return [parseFloat(point.split(',')[1]), parseFloat(point.split(',')[0])]; }); object[fieldName] = { __type: 'Polygon', - coordinates: coords, + coordinates: updatedCoords, }; } if (object[fieldName] && schema.fields[fieldName].type === 'File') { @@ -2634,7 +2634,7 @@ function literalizeRegexPart(s: string) { const result1: any = s.match(matcher1); if (result1 && result1.length > 1 && result1.index > -1) { // process regex that has a beginning and an end specified for the literal text - const prefix = s.substr(0, result1.index); + const prefix = s.substring(0, result1.index); const remaining = result1[1]; return literalizeRegexPart(prefix) + createLiteralRegex(remaining); @@ -2644,7 +2644,7 @@ function literalizeRegexPart(s: string) { const matcher2 = /\\Q((?!\\E).*)$/; const result2: any = s.match(matcher2); if (result2 && result2.length > 1 && result2.index > -1) { - const prefix = s.substr(0, result2.index); + const prefix = s.substring(0, result2.index); const remaining = result2[1]; return literalizeRegexPart(prefix) + createLiteralRegex(remaining); diff --git a/src/Config.js b/src/Config.js index 5e3a49bb35..8fe10a9a1c 100644 --- a/src/Config.js +++ b/src/Config.js @@ -25,7 +25,7 @@ function removeTrailingSlash(str) { return str; } if (str.endsWith('/')) { - str = str.substr(0, str.length - 1); + str = str.substring(0, str.length - 1); } return str; } From 44acd6d9ed157ad4842200c9d01f9c77a05fec3a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 20 Jun 2023 20:10:25 +1000 Subject: [PATCH 06/16] feat: Add conditional email verification via dynamic Parse Server options `verifyUserEmails`, `sendUserEmailVerification` that now accept functions (#8425) --- resources/buildConfigDefinitions.js | 11 +- spec/EmailVerificationToken.spec.js | 178 ++++++++++++++++++++++++++++ spec/UserController.spec.js | 21 ++-- src/Controllers/UserController.js | 100 ++++++++++------ src/Options/Definitions.js | 9 +- src/Options/docs.js | 3 +- src/Options/index.js | 10 +- src/RestWrite.js | 51 ++++++-- src/Routers/PagesRouter.js | 2 +- src/Routers/PublicAPIRouter.js | 2 +- src/Routers/UsersRouter.js | 34 +++--- 11 files changed, 340 insertions(+), 81 deletions(-) diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index e0d33daa4b..0be6e0085d 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -255,7 +255,16 @@ function inject(t, list) { props.push(t.objectProperty(t.stringLiteral('action'), action)); } if (elt.defaultValue) { - const parsedValue = parseDefaultValue(elt, elt.defaultValue, t); + let parsedValue = parseDefaultValue(elt, elt.defaultValue, t); + if (!parsedValue) { + for (const type of elt.typeAnnotation.types) { + elt.type = type.type; + parsedValue = parseDefaultValue(elt, elt.defaultValue, t); + if (parsedValue) { + break; + } + } + } if (parsedValue) { props.push(t.objectProperty(t.stringLiteral('default'), parsedValue)); } else { diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index e21a049719..a7a59b893e 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -288,6 +288,184 @@ describe('Email Verification Token Expiration: ', () => { }); }); + it('can conditionally send emails', async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const verifyUserEmails = { + method(req) { + expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']); + return false; + }, + }; + const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough(); + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: verifyUserEmails.method, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }); + const beforeSave = { + method(req) { + req.object.set('emailVerified', true); + }, + }; + const saveSpy = spyOn(beforeSave, 'method').and.callThrough(); + const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); + Parse.Cloud.beforeSave(Parse.User, beforeSave.method); + const user = new Parse.User(); + user.setUsername('sets_email_verify_token_expires_at'); + user.setPassword('expiringToken'); + user.set('email', 'user@example.com'); + await user.signUp(); + + const config = Config.get('test'); + const results = await config.database.find( + '_User', + { + username: 'sets_email_verify_token_expires_at', + }, + {}, + Auth.maintenance(config) + ); + + expect(results.length).toBe(1); + const user_data = results[0]; + expect(typeof user_data).toBe('object'); + expect(user_data.emailVerified).toEqual(true); + expect(user_data._email_verify_token).toBeUndefined(); + expect(user_data._email_verify_token_expires_at).toBeUndefined(); + expect(emailSpy).not.toHaveBeenCalled(); + expect(saveSpy).toHaveBeenCalled(); + expect(sendEmailOptions).toBeUndefined(); + expect(verifySpy).toHaveBeenCalled(); + }); + + it('can conditionally send emails and allow conditional login', async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const verifyUserEmails = { + method(req) { + expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']); + if (req.object.get('username') === 'no_email') { + return false; + } + return true; + }, + }; + const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough(); + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: verifyUserEmails.method, + preventLoginWithUnverifiedEmail: verifyUserEmails.method, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }); + const user = new Parse.User(); + user.setUsername('no_email'); + user.setPassword('expiringToken'); + user.set('email', 'user@example.com'); + await user.signUp(); + expect(sendEmailOptions).toBeUndefined(); + expect(user.getSessionToken()).toBeDefined(); + expect(verifySpy).toHaveBeenCalledTimes(2); + const user2 = new Parse.User(); + user2.setUsername('email'); + user2.setPassword('expiringToken'); + user2.set('email', 'user2@example.com'); + await user2.signUp(); + expect(user2.getSessionToken()).toBeUndefined(); + expect(sendEmailOptions).toBeDefined(); + expect(verifySpy).toHaveBeenCalledTimes(4); + }); + + it('can conditionally send user email verification', async () => { + const emailAdapter = { + sendVerificationEmail: () => {}, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + const sendVerificationEmail = { + method(req) { + expect(req.user).toBeDefined(); + expect(req.master).toBeDefined(); + return false; + }, + }; + const sendSpy = spyOn(sendVerificationEmail, 'method').and.callThrough(); + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + sendUserEmailVerification: sendVerificationEmail.method, + }); + const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); + const newUser = new Parse.User(); + newUser.setUsername('unsets_email_verify_token_expires_at'); + newUser.setPassword('expiringToken'); + newUser.set('email', 'user@example.com'); + await newUser.signUp(); + await Parse.User.requestEmailVerification('user@example.com'); + expect(sendSpy).toHaveBeenCalledTimes(2); + expect(emailSpy).toHaveBeenCalledTimes(0); + }); + + it('beforeSave options do not change existing behaviour', async () => { + let sendEmailOptions; + const emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {}, + }; + await reconfigureServer({ + appName: 'emailVerifyToken', + verifyUserEmails: true, + emailAdapter: emailAdapter, + emailVerifyTokenValidityDuration: 5, // 5 seconds + publicServerURL: 'http://localhost:8378/1', + }); + const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough(); + const newUser = new Parse.User(); + newUser.setUsername('unsets_email_verify_token_expires_at'); + newUser.setPassword('expiringToken'); + newUser.set('email', 'user@parse.com'); + await newUser.signUp(); + const response = await request({ + url: sendEmailOptions.link, + followRedirects: false, + }); + expect(response.status).toEqual(302); + const config = Config.get('test'); + const results = await config.database.find('_User', { + username: 'unsets_email_verify_token_expires_at', + }); + + expect(results.length).toBe(1); + const user = results[0]; + expect(typeof user).toBe('object'); + expect(user.emailVerified).toEqual(true); + expect(typeof user._email_verify_token).toBe('undefined'); + expect(typeof user._email_verify_token_expires_at).toBe('undefined'); + expect(emailSpy).toHaveBeenCalled(); + }); + it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => { const user = new Parse.User(); let sendEmailOptions; diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js index 6bcc454baf..7b98367702 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -1,4 +1,3 @@ -const UserController = require('../lib/Controllers/UserController').UserController; const emailAdapter = require('./support/MockEmailAdapter'); describe('UserController', () => { @@ -11,11 +10,14 @@ describe('UserController', () => { describe('sendVerificationEmail', () => { describe('parseFrameURL not provided', () => { it('uses publicServerURL', async done => { - await reconfigureServer({ + const server = await reconfigureServer({ publicServerURL: 'http://www.example.com', customPages: { parseFrameURL: undefined, }, + verifyUserEmails: true, + emailAdapter, + appName: 'test', }); emailAdapter.sendVerificationEmail = options => { expect(options.link).toEqual( @@ -24,20 +26,20 @@ describe('UserController', () => { emailAdapter.sendVerificationEmail = () => Promise.resolve(); done(); }; - const userController = new UserController(emailAdapter, 'test', { - verifyUserEmails: true, - }); - userController.sendVerificationEmail(user); + server.config.userController.sendVerificationEmail(user); }); }); describe('parseFrameURL provided', () => { it('uses parseFrameURL and includes the destination in the link parameter', async done => { - await reconfigureServer({ + const server = await reconfigureServer({ publicServerURL: 'http://www.example.com', customPages: { parseFrameURL: 'http://someother.example.com/handle-parse-iframe', }, + verifyUserEmails: true, + emailAdapter, + appName: 'test', }); emailAdapter.sendVerificationEmail = options => { expect(options.link).toEqual( @@ -46,10 +48,7 @@ describe('UserController', () => { emailAdapter.sendVerificationEmail = () => Promise.resolve(); done(); }; - const userController = new UserController(emailAdapter, 'test', { - verifyUserEmails: true, - }); - userController.sendVerificationEmail(user); + server.config.userController.sendVerificationEmail(user); }); }); }); diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index 6871add987..7618f500bf 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -32,20 +32,33 @@ export class UserController extends AdaptableController { } get shouldVerifyEmails() { - return this.options.verifyUserEmails; + return (this.config || this.options).verifyUserEmails; } - setEmailVerifyToken(user) { - if (this.shouldVerifyEmails) { - user._email_verify_token = randomString(25); + async setEmailVerifyToken(user, req, storage = {}) { + let shouldSendEmail = this.shouldVerifyEmails; + if (typeof shouldSendEmail === 'function') { + const response = await Promise.resolve(shouldSendEmail(req)); + shouldSendEmail = response !== false; + } + if (!shouldSendEmail) { + return false; + } + storage.sendVerificationEmail = true; + user._email_verify_token = randomString(25); + if ( + !storage.fieldsChangedByTrigger || + !storage.fieldsChangedByTrigger.includes('emailVerified') + ) { user.emailVerified = false; + } - if (this.config.emailVerifyTokenValidityDuration) { - user._email_verify_token_expires_at = Parse._encode( - this.config.generateEmailVerifyTokenExpiresAt() - ); - } + if (this.config.emailVerifyTokenValidityDuration) { + user._email_verify_token_expires_at = Parse._encode( + this.config.generateEmailVerifyTokenExpiresAt() + ); } + return true; } verifyEmail(username, token) { @@ -131,27 +144,39 @@ export class UserController extends AdaptableController { }); } - sendVerificationEmail(user) { + async sendVerificationEmail(user, req) { if (!this.shouldVerifyEmails) { return; } const token = encodeURIComponent(user._email_verify_token); // We may need to fetch the user in case of update email - this.getUserIfNeeded(user).then(user => { - const username = encodeURIComponent(user.username); - - const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config); - const options = { - appName: this.config.appName, - link: link, - user: inflate('_User', user), - }; - if (this.adapter.sendVerificationEmail) { - this.adapter.sendVerificationEmail(options); - } else { - this.adapter.sendMail(this.defaultVerificationEmail(options)); - } - }); + const fetchedUser = await this.getUserIfNeeded(user); + let shouldSendEmail = this.config.sendUserEmailVerification; + if (typeof shouldSendEmail === 'function') { + const response = await Promise.resolve( + this.config.sendUserEmailVerification({ + user: Parse.Object.fromJSON({ className: '_User', ...fetchedUser }), + master: req.auth?.isMaster, + }) + ); + shouldSendEmail = !!response; + } + if (!shouldSendEmail) { + return; + } + const username = encodeURIComponent(user.username); + + const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config); + const options = { + appName: this.config.appName, + link: link, + user: inflate('_User', fetchedUser), + }; + if (this.adapter.sendVerificationEmail) { + this.adapter.sendVerificationEmail(options); + } else { + this.adapter.sendMail(this.defaultVerificationEmail(options)); + } } /** @@ -160,7 +185,7 @@ export class UserController extends AdaptableController { * @param user * @returns {*} */ - regenerateEmailVerifyToken(user) { + 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') { @@ -174,19 +199,22 @@ export class UserController extends AdaptableController { ) { return Promise.resolve(); } - this.setEmailVerifyToken(user); + const shouldSend = await this.setEmailVerifyToken(user, { user, master }); + if (!shouldSend) { + return; + } return this.config.database.update('_User', { username: user.username }, user); } - resendVerificationEmail(username) { - return this.getUserIfNeeded({ username: username }).then(aUser => { - if (!aUser || aUser.emailVerified) { - throw undefined; - } - return this.regenerateEmailVerifyToken(aUser).then(() => { - this.sendVerificationEmail(aUser); - }); - }); + async resendVerificationEmail(username, req) { + const aUser = await this.getUserIfNeeded({ username: username }); + if (!aUser || aUser.emailVerified) { + throw undefined; + } + const generate = await this.regenerateEmailVerifyToken(aUser, req.auth?.isMaster); + if (generate) { + this.sendVerificationEmail(aUser, req); + } } setPasswordResetToken(email) { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 3815902c51..b067412d26 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -496,6 +496,12 @@ module.exports.ParseServerOptions = { action: parsers.objectParser, default: {}, }, + sendUserEmailVerification: { + env: 'PARSE_SERVER_SEND_USER_EMAIL_VERIFICATION', + help: + 'Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.

Default is `true`.
', + default: true, + }, serverCloseComplete: { env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE', help: 'Callback when server has closed', @@ -542,8 +548,7 @@ module.exports.ParseServerOptions = { verifyUserEmails: { env: 'PARSE_SERVER_VERIFY_USER_EMAILS', help: - 'Set to `true` to require users to verify their email address to complete the sign-up process.

Default is `false`.', - action: parsers.booleanParser, + 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.

Default is `false`.', default: false, }, webhookKey: { diff --git a/src/Options/docs.js b/src/Options/docs.js index 847e7df944..2e1390345d 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -89,6 +89,7 @@ * @property {Boolean} scheduledPush Configuration for push scheduling, defaults to false. * @property {SchemaOptions} schema Defined schema * @property {SecurityOptions} security The security options to identify and report weak security settings. + * @property {Boolean} sendUserEmailVerification Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.

Default is `true`.
* @property {Function} serverCloseComplete Callback when server has closed * @property {String} serverURL URL to your parse server with http:// or https://. * @property {Number} sessionLength Session duration, in seconds, defaults to 1 year @@ -97,7 +98,7 @@ * @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the express trust proxy settings documentation. Defaults to `false`. * @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields * @property {Boolean} verbose Set the logging to verbose - * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process.

Default is `false`. + * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.

Default is `false`. * @property {String} webhookKey Key sent with outgoing webhook calls */ diff --git a/src/Options/index.js b/src/Options/index.js index 87813147f7..2008301c7d 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -153,11 +153,11 @@ export interface ParseServerOptions { /* Max file size for uploads, defaults to 20mb :DEFAULT: 20mb */ maxUploadSize: ?string; - /* Set to `true` to require users to verify their email address to complete the sign-up process. + /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.

Default is `false`. :DEFAULT: false */ - verifyUserEmails: ?boolean; + verifyUserEmails: ?(boolean | void); /* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.

Default is `false`. @@ -188,6 +188,12 @@ export interface ParseServerOptions { Requires option `verifyUserEmails: true`. :DEFAULT: false */ emailVerifyTokenReuseIfValid: ?boolean; + /* Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending. +

+ Default is `true`. +
+ :DEFAULT: true */ + sendUserEmailVerification: ?(boolean | void); /* The account lockout policy for failed login attempts. */ accountLockout: ?AccountLockoutOptions; /* The password policy for enforcing password related rules. */ diff --git a/src/RestWrite.js b/src/RestWrite.js index f7c6a53592..003a4a7d0a 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -113,6 +113,9 @@ RestWrite.prototype.execute = function () { .then(() => { return this.validateAuthData(); }) + .then(() => { + return this.checkRestrictedFields(); + }) .then(() => { return this.runBeforeSaveTrigger(); }) @@ -603,17 +606,23 @@ RestWrite.prototype.handleAuthData = async function (authData) { } }; -// The non-third-party parts of User transformation -RestWrite.prototype.transformUser = function () { - var promise = Promise.resolve(); +RestWrite.prototype.checkRestrictedFields = async function () { if (this.className !== '_User') { - return promise; + return; } if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) { const error = `Clients aren't allowed to manually update email verification.`; throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); } +}; + +// The non-third-party parts of User transformation +RestWrite.prototype.transformUser = function () { + var promise = Promise.resolve(); + if (this.className !== '_User') { + return promise; + } // Do not cleanup session if objectId is not set if (this.query && this.objectId()) { @@ -751,8 +760,14 @@ RestWrite.prototype._validateEmail = function () { Object.keys(this.data.authData)[0] === 'anonymous') ) { // We updated the email, send a new validation - this.storage['sendVerificationEmail'] = true; - this.config.userController.setEmailVerifyToken(this.data); + const { originalObject, updatedObject } = this.buildParseObjects(); + const request = { + original: originalObject, + object: updatedObject, + master: this.auth.isMaster, + ip: this.config.ip, + }; + return this.config.userController.setEmailVerifyToken(this.data, request, this.storage); } }); }; @@ -864,7 +879,7 @@ RestWrite.prototype._validatePasswordHistory = function () { return Promise.resolve(); }; -RestWrite.prototype.createSessionTokenIfNeeded = function () { +RestWrite.prototype.createSessionTokenIfNeeded = async function () { if (this.className !== '_User') { return; } @@ -878,13 +893,31 @@ RestWrite.prototype.createSessionTokenIfNeeded = function () { } if ( !this.storage.authProvider && // signup call, with - this.config.preventLoginWithUnverifiedEmail && // no login without verification + 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; + } + } return this.createSessionToken(); }; @@ -1010,7 +1043,7 @@ RestWrite.prototype.handleFollowup = function () { if (this.storage && this.storage['sendVerificationEmail']) { delete this.storage['sendVerificationEmail']; // Fire and forget! - this.config.userController.sendVerificationEmail(this.data); + this.config.userController.sendVerificationEmail(this.data, { auth: this.auth }); return this.handleFollowup.bind(this); } }; diff --git a/src/Routers/PagesRouter.js b/src/Routers/PagesRouter.js index 5d5a1467a7..79a487b6e4 100644 --- a/src/Routers/PagesRouter.js +++ b/src/Routers/PagesRouter.js @@ -125,7 +125,7 @@ export class PagesRouter extends PromiseRouter { const userController = config.userController; - return userController.resendVerificationEmail(username).then( + return userController.resendVerificationEmail(username, req).then( () => { return this.goToPage(req, pages.emailVerificationSendSuccess); }, diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index 5009ee7d22..ddef76a5b8 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -63,7 +63,7 @@ export class PublicAPIRouter extends PromiseRouter { const userController = config.userController; - return userController.resendVerificationEmail(username).then( + return userController.resendVerificationEmail(username, req).then( () => { return Promise.resolve({ status: 302, diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index feca46e802..e58f3dda6d 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -447,7 +447,7 @@ export class UsersRouter extends ClassesRouter { } } - handleVerificationEmailRequest(req) { + async handleVerificationEmailRequest(req) { this._throwOnBadEmailConfig(req); const { email } = req.body; @@ -461,25 +461,25 @@ export class UsersRouter extends ClassesRouter { ); } - return req.config.database.find('_User', { email: email }).then(results => { - if (!results.length || results.length < 1) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`); - } - const user = results[0]; + 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}`); + } + const user = results[0]; - // remove password field, messes with saving on postgres - delete user.password; + // remove password field, messes with saving on postgres + delete user.password; - if (user.emailVerified) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, `Email ${email} is already verified.`); - } + if (user.emailVerified) { + throw new Parse.Error(Parse.Error.OTHER_CAUSE, `Email ${email} is already verified.`); + } - const userController = req.config.userController; - return userController.regenerateEmailVerifyToken(user).then(() => { - userController.sendVerificationEmail(user); - return { response: {} }; - }); - }); + const userController = req.config.userController; + const send = await userController.regenerateEmailVerifyToken(user, req.auth.isMaster); + if (send) { + userController.sendVerificationEmail(user, req); + } + return { response: {} }; } async handleChallenge(req) { From e6bd2baf718a7dee36ae67291bb4be64cf4034bf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 20 Jun 2023 10:12:02 +0000 Subject: [PATCH 07/16] chore(release): 6.3.0-alpha.2 [skip ci] # [6.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.1...6.3.0-alpha.2) (2023-06-20) ### Features * Add conditional email verification via dynamic Parse Server options `verifyUserEmails`, `sendUserEmailVerification` that now accept functions ([#8425](https://github.com/parse-community/parse-server/issues/8425)) ([44acd6d](https://github.com/parse-community/parse-server/commit/44acd6d9ed157ad4842200c9d01f9c77a05fec3a)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index c7a229e5a7..14170fdf58 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [6.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/6.3.0-alpha.1...6.3.0-alpha.2) (2023-06-20) + + +### Features + +* Add conditional email verification via dynamic Parse Server options `verifyUserEmails`, `sendUserEmailVerification` that now accept functions ([#8425](https://github.com/parse-community/parse-server/issues/8425)) ([44acd6d](https://github.com/parse-community/parse-server/commit/44acd6d9ed157ad4842200c9d01f9c77a05fec3a)) + # [6.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/6.2.0...6.3.0-alpha.1) (2023-06-18) diff --git a/package-lock.json b/package-lock.json index 146ac05e7e..4d04dca6cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-server", - "version": "6.3.0-alpha.1", + "version": "6.3.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "6.3.0-alpha.1", + "version": "6.3.0-alpha.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 7a58f71970..1d57c96e8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "6.3.0-alpha.1", + "version": "6.3.0-alpha.2", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 1850be45b17abdccae129811157ddef6feb52a34 Mon Sep 17 00:00:00 2001 From: Parse Platform <90459499+parseplatformorg@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:42:42 +0200 Subject: [PATCH 08/16] refactor: Security upgrade semver from 7.5.1 to 7.5.2 (#8650) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d04dca6cc..0ddcbdbeb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.6", - "semver": "7.5.1", + "semver": "^7.5.2", "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.0", @@ -18178,9 +18178,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -34406,9 +34406,9 @@ } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "requires": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 1d57c96e8b..9ffb91d2a6 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "pluralize": "8.0.0", "rate-limit-redis": "3.0.2", "redis": "4.6.6", - "semver": "7.5.1", + "semver": "7.5.2", "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "9.0.0", From dc466087561c6e0181281ee4d49f161db41368b6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 22 Jun 2023 12:46:40 +1000 Subject: [PATCH 09/16] Update src/Options/index.js Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Daniel --- src/Options/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Options/index.js b/src/Options/index.js index e339b5978d..bd320d549a 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -196,9 +196,9 @@ export interface ParseServerOptions { cacheAdapter: ?Adapter; /* Adapter module for email sending */ emailAdapter: ?Adapter; - /* Whether Parse Pointers should be encoded in Cloud Code. + /* If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. :DEFAULT: false */ - encodeCloudPointers: ?boolean; + encodePointerInCloudFunction: ?boolean; /* Public URL to your parse server with http:// or https://. :ENV: PARSE_PUBLIC_SERVER_URL */ publicServerURL: ?string; From aa241ae8db03e55ab8aa38c100a4ef4d4a722761 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 22 Jun 2023 12:47:04 +1000 Subject: [PATCH 10/16] definitions --- src/Options/Definitions.js | 7 ++++--- src/Options/docs.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 8cf33667f1..c9c0161cac 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -210,9 +210,10 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, - encodeCloudPointers: { - env: 'PARSE_SERVER_ENCODE_CLOUD_POINTERS', - help: 'Whether Parse Pointers should be encoded in Cloud Code.', + encodePointerInCloudFunction: { + env: 'PARSE_SERVER_ENCODE_POINTER_IN_CLOUD_FUNCTION', + help: + 'If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

\u2139\uFE0F The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`.', action: parsers.booleanParser, default: false, }, diff --git a/src/Options/docs.js b/src/Options/docs.js index 8c447368a2..31bf198daf 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -40,7 +40,7 @@ * @property {Number} emailVerifyTokenValidityDuration Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to `undefined`, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is `undefined`.
Requires option `verifyUserEmails: true`. * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors - * @property {Boolean} encodeCloudPointers Whether Parse Pointers should be encoded in Cloud Code. + * @property {Boolean} encodePointerInCloudFunction If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. * @property {String} encryptionKey Key for encrypting your files * @property {Boolean} enforcePrivateUsers Set to true if new users should be created without public read and write access. * @property {Boolean} expireInactiveSessions Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date. From ff3cbb36f0a5c74abe70f129a4d5497a529d2afe Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 23 Jun 2023 11:32:51 +1000 Subject: [PATCH 11/16] add deprecated --- DEPRECATIONS.md | 1 + src/Deprecator/Deprecations.js | 1 + 2 files changed, 2 insertions(+) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 56359c937e..ebffe36f20 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -13,6 +13,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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 | `encodePointerInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 7.0.0 (2024) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 0afd98ff0c..4980853d8d 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -18,4 +18,5 @@ module.exports = [ { optionKey: 'allowClientClassCreation', changeNewDefault: 'false' }, { optionKey: 'allowExpiredAuthDataToken', changeNewDefault: 'false' }, + { optionKey: 'encodePointerInCloudFunction', changeNewDefault: 'true' }, ]; From 755e967cf5953071a6066f994e513c5b3914aa96 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 23 Jun 2023 12:33:41 +1000 Subject: [PATCH 12/16] rename --- DEPRECATIONS.md | 2 +- spec/CloudCode.spec.js | 2 +- src/Deprecator/Deprecations.js | 2 +- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- src/Options/index.js | 2 +- src/Routers/FunctionsRouter.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index ebffe36f20..6674c18808 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -13,7 +13,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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 | `encodePointerInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 7.0.0 (2024) | deprecated | - | +| DEPPS10 | `encodeParseObjectInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 7.0.0 (2024) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 939f373924..a2e623551a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1373,7 +1373,7 @@ describe('Cloud Code', () => { }); it('allow cloud to encode Parse Objects', async () => { - await reconfigureServer({ encodeCloudPointers: true }); + await reconfigureServer({ encodeParseObjectInCloudFunction: true }); const user = new Parse.User(); user.setUsername('username'); user.setPassword('password'); diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 4980853d8d..2f698ad33e 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -18,5 +18,5 @@ module.exports = [ { optionKey: 'allowClientClassCreation', changeNewDefault: 'false' }, { optionKey: 'allowExpiredAuthDataToken', changeNewDefault: 'false' }, - { optionKey: 'encodePointerInCloudFunction', changeNewDefault: 'true' }, + { optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' }, ]; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index d514901448..7fa7f10362 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -210,7 +210,7 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, - encodePointerInCloudFunction: { + encodeParseObjectInCloudFunction: { env: 'PARSE_SERVER_ENCODE_POINTER_IN_CLOUD_FUNCTION', help: 'If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

\u2139\uFE0F The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`.', diff --git a/src/Options/docs.js b/src/Options/docs.js index 6d24eb6d34..b0a7f24126 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -40,7 +40,7 @@ * @property {Number} emailVerifyTokenValidityDuration Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to `undefined`, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is `undefined`.
Requires option `verifyUserEmails: true`. * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors - * @property {Boolean} encodePointerInCloudFunction If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. + * @property {Boolean} encodeParseObjectInCloudFunction If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. * @property {String} encryptionKey Key for encrypting your files * @property {Boolean} enforcePrivateUsers Set to true if new users should be created without public read and write access. * @property {Boolean} expireInactiveSessions Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date. diff --git a/src/Options/index.js b/src/Options/index.js index 7f7fea47e7..201a703854 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -204,7 +204,7 @@ export interface ParseServerOptions { emailAdapter: ?Adapter; /* If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. :DEFAULT: false */ - encodePointerInCloudFunction: ?boolean; + encodeParseObjectInCloudFunction: ?boolean; /* Public URL to your parse server with http:// or https://. :ENV: PARSE_PUBLIC_SERVER_URL */ publicServerURL: ?string; diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index de61893c03..bb4b959ebe 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -18,7 +18,7 @@ function parseObject(obj, config) { return Object.assign(new Date(obj.iso), obj); } else if (obj && obj.__type == 'File') { return Parse.File.fromJSON(obj); - } else if (obj && obj.__type == 'Pointer' && config.encodeCloudPointers) { + } else if (obj && obj.__type == 'Pointer' && config.encodeParseObjectInCloudFunction) { return Parse.Object.fromJSON({ __type: 'Pointer', className: obj.className, From ffdb88231ba7f31999e273682761a73da5b04603 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 23 Jun 2023 13:13:51 +1000 Subject: [PATCH 13/16] Update Definitions.js --- src/Options/Definitions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 7fa7f10362..96bc368718 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -211,7 +211,7 @@ module.exports.ParseServerOptions = { default: false, }, encodeParseObjectInCloudFunction: { - env: 'PARSE_SERVER_ENCODE_POINTER_IN_CLOUD_FUNCTION', + env: 'PARSE_SERVER_ENCODE_PARSE_OBJECT_IN_CLOUD_FUNCTION', help: 'If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

\u2139\uFE0F The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`.', action: parsers.booleanParser, From ab87a4925b41288cb4ba5a4f0edcb85cdc01bd2d Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:49:59 +0200 Subject: [PATCH 14/16] Update DEPRECATIONS.md Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- DEPRECATIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 6674c18808..59b728a944 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -13,7 +13,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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 | `encodeParseObjectInCloudFunction` defaults to `true` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.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) | 7.0.0 (2024) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." From 1debec23d337646938cb9e83e1c086c047e31107 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:50:13 +0200 Subject: [PATCH 15/16] Update src/Options/index.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/Options/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/index.js b/src/Options/index.js index 201a703854..a8414c658c 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -202,7 +202,7 @@ export interface ParseServerOptions { cacheAdapter: ?Adapter; /* Adapter module for email sending */ emailAdapter: ?Adapter; - /* If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. + /* If set to `true`, a `Parse.Object` that is in the payload when calling a Cloud Function will be converted to an instance of `Parse.Object`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Object` but is not an actual instance of `Parse.Object`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Object`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where JavaScript objects are not converted to actual instances of `Parse.Object`. :DEFAULT: false */ encodeParseObjectInCloudFunction: ?boolean; /* Public URL to your parse server with http:// or https://. From 101ff16aa80ef767a02d0d444c7a8ce08b70e83a Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:39:53 +0200 Subject: [PATCH 16/16] update defs --- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 96bc368718..6477836eed 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -213,7 +213,7 @@ module.exports.ParseServerOptions = { encodeParseObjectInCloudFunction: { env: 'PARSE_SERVER_ENCODE_PARSE_OBJECT_IN_CLOUD_FUNCTION', help: - 'If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

\u2139\uFE0F The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`.', + 'If set to `true`, a `Parse.Object` that is in the payload when calling a Cloud Function will be converted to an instance of `Parse.Object`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Object` but is not an actual instance of `Parse.Object`. Default is `false`.

\u2139\uFE0F The expected behavior would be that the object is converted to an instance of `Parse.Object`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where JavaScript objects are not converted to actual instances of `Parse.Object`.', action: parsers.booleanParser, default: false, }, diff --git a/src/Options/docs.js b/src/Options/docs.js index b0a7f24126..fdb62bb590 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -40,7 +40,7 @@ * @property {Number} emailVerifyTokenValidityDuration Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to `undefined`, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is `undefined`.
Requires option `verifyUserEmails: true`. * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors - * @property {Boolean} encodeParseObjectInCloudFunction If set to `true`, a `Parse.Pointer` that is in the payload when calling a Cloud Function will be converted to an instance of a `Parse.Pointer`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Pointer` but is not an actual instance of a `Parse.Pointer`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Pointer`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where `Parse.Pointer` objects are not converted to actual instances of `Parse.Pointer`. + * @property {Boolean} encodeParseObjectInCloudFunction If set to `true`, a `Parse.Object` that is in the payload when calling a Cloud Function will be converted to an instance of `Parse.Object`. If `false`, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a `Parse.Object` but is not an actual instance of `Parse.Object`. Default is `false`.

ℹ️ The expected behavior would be that the object is converted to an instance of `Parse.Object`, so you would normally set this option to `true`. The default is `false` because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where JavaScript objects are not converted to actual instances of `Parse.Object`. * @property {String} encryptionKey Key for encrypting your files * @property {Boolean} enforcePrivateUsers Set to true if new users should be created without public read and write access. * @property {Boolean} expireInactiveSessions Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date.