From 705b1ef14dc17ecea881fc75c0180c230432e9a7 Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:06:14 +0100 Subject: [PATCH 1/5] feat: add deprecation on explain --- DEPRECATIONS.md | 3 ++- changelogs/CHANGELOG_alpha.md | 6 ++++++ spec/ParseQuery.spec.js | 36 +++++++++++++++++++++++++++++++++++ src/rest.js | 14 ++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index eb7f463638..76782de356 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -14,7 +14,8 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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) | removed | - | | 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) | removed | - | | DEPPS10 | Encode `Parse.Object` in Cloud Function and remove option `encodeParseObjectInCloudFunction` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 9.0.0 (2026) | deprecated | - | -| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | deprecated | - | +| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) +| DEPPS12 | Restrict `explain` query parameter to master key | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 5e04ed3c75..5e5ecfbca3 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,9 @@ +# [8.3.0-alpha.13](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.12...8.3.0-alpha.13) (2025-10-25) + +### Features + +- Deprecation DEPPS12: `explain` query parameter without master key ([#7519](https://github.com/parse-community/parse-server/issues/7519)) ([DEPPS12](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md#depps12)) + # [8.3.0-alpha.12](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.11...8.3.0-alpha.12) (2025-10-25) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 98ef70564f..5d3086f0e4 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -5384,4 +5384,40 @@ describe('Parse.Query testing', () => { expect(query1.length).toEqual(1); }); }); + + it_id('DEPPS12')(it_only_db('mongo'))( + 'throws error when using explain without master key', + async () => { + const obj = new TestObject({ foo: 'bar' }); + await obj.save(); + + const spyLogRuntimeDeprecation = spyOn(Deprecator, 'logRuntimeDeprecation'); + + // Test that explain without master key throws an error + const query = new Parse.Query(TestObject); + query.explain(); + + try { + await query.find(); + + expect(spyLogRuntimeDeprecation).toHaveBeenCalledTimes(1); + expect(spyLogRuntimeDeprecation).toHaveBeenCalledWith({ + usage: 'Using the explain query parameter without the master key', + }); + // fail('Should have thrown an error'); + } catch (error) { + // Uncomment this after the Deprecation DEPPS12 + // expect(error.code).toEqual(Parse.Error.INVALID_QUERY); + // expect(error.message).toEqual('Using the explain query parameter without the master key'); + } + + // Test that explain with master key works fine + const queryWithMasterKey = new Parse.Query(TestObject); + queryWithMasterKey.explain(); + const result = await queryWithMasterKey.find({ useMasterKey: true }); + + // Should return explain result (not throw error) + expect(result).toBeDefined(); + } + ); }); diff --git a/src/rest.js b/src/rest.js index 8297121a68..fc7027c4bb 100644 --- a/src/rest.js +++ b/src/rest.js @@ -13,6 +13,7 @@ var RestQuery = require('./RestQuery'); var RestWrite = require('./RestWrite'); var triggers = require('./triggers'); const { enforceRoleSecurity } = require('./SharedRest'); +const Deprecator = require('./Deprecator/Deprecator'); function checkTriggers(className, config, types) { return types.some(triggerType => { @@ -35,6 +36,19 @@ async function runFindTriggers( ) { const { isGet } = options; + if (restOptions && restOptions.explain && !auth.isMaster) { + // After the Deprecation DEPPS12 uncomment this to throw an error + // throw new Parse.Error( + // Parse.Error.INVALID_QUERY, + // 'Using the explain query parameter without the master key' + // ); + + // Deprecation DEPPS12 + Deprecator.logRuntimeDeprecation({ + usage: 'Using the explain query parameter without the master key', + }); + } + // Run beforeFind trigger - may modify query or return objects directly const result = await triggers.maybeRunQueryTrigger( triggers.Types.beforeFind, From 02c2eaa6d4424e335d49cc7408c31ea111812759 Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:53:59 +0100 Subject: [PATCH 2/5] fix: revie --- DEPRECATIONS.md | 4 ++-- spec/ParseQuery.spec.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 76782de356..656f70f221 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -14,8 +14,8 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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) | removed | - | | 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) | removed | - | | DEPPS10 | Encode `Parse.Object` in Cloud Function and remove option `encodeParseObjectInCloudFunction` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 9.0.0 (2026) | deprecated | - | -| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) -| DEPPS12 | Restrict `explain` query parameter to master key | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | | deprecated | - | +| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | deprecated | - | +| DEPPS12 | Restrict `explain` query parameter to master key | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 5d3086f0e4..19b27c2dbd 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -8,6 +8,7 @@ const Parse = require('parse/node'); const request = require('../lib/request'); const ParseServerRESTController = require('../lib/ParseServerRESTController').ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; +import Deprecator from '../lib/Deprecator/Deprecator'; const masterKeyHeaders = { 'X-Parse-Application-Id': 'test', From bb8eedb967fcd7353d0011d0e65b9a19a8e0a888 Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:15:09 +0100 Subject: [PATCH 3/5] fix: esm --- spec/ParseQuery.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 19b27c2dbd..b433712cbc 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -8,7 +8,7 @@ const Parse = require('parse/node'); const request = require('../lib/request'); const ParseServerRESTController = require('../lib/ParseServerRESTController').ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; -import Deprecator from '../lib/Deprecator/Deprecator'; +const Deprecator = require('../lib/Deprecator/Deprecator'); const masterKeyHeaders = { 'X-Parse-Application-Id': 'test', From 50b2080e370fd8d6211eefb961ad7d2a4357c25b Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:24:53 +0100 Subject: [PATCH 4/5] fix: feedbacks --- spec/ParseQuery.spec.js | 87 ++++++++++++++----- .../Storage/Mongo/MongoStorageAdapter.js | 9 +- src/Config.js | 5 ++ src/Deprecator/Deprecations.js | 6 ++ src/Options/Definitions.js | 7 ++ src/Options/docs.js | 1 + src/Options/index.js | 3 + src/rest.js | 17 ++-- 8 files changed, 103 insertions(+), 32 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 5d3086f0e4..04bb46b678 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -5385,39 +5385,86 @@ describe('Parse.Query testing', () => { }); }); - it_id('DEPPS12')(it_only_db('mongo'))( - 'throws error when using explain without master key', + it_id('a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d')(it_only_db('mongo'))( + 'explain works with and without master key when allowPublicExplain is true', async () => { + await reconfigureServer({ + databaseURI: 'mongodb://localhost:27017/parse', + databaseAdapter: null, + databaseOptions: { + allowPublicExplain: true, + }, + }); + const obj = new TestObject({ foo: 'bar' }); await obj.save(); - const spyLogRuntimeDeprecation = spyOn(Deprecator, 'logRuntimeDeprecation'); - - // Test that explain without master key throws an error + // Without master key const query = new Parse.Query(TestObject); query.explain(); + const resultWithoutMasterKey = await query.find(); + expect(resultWithoutMasterKey).toBeDefined(); - try { - await query.find(); + // With master key + const queryWithMasterKey = new Parse.Query(TestObject); + queryWithMasterKey.explain(); + const resultWithMasterKey = await queryWithMasterKey.find({ useMasterKey: true }); + expect(resultWithMasterKey).toBeDefined(); + } + ); - expect(spyLogRuntimeDeprecation).toHaveBeenCalledTimes(1); - expect(spyLogRuntimeDeprecation).toHaveBeenCalledWith({ - usage: 'Using the explain query parameter without the master key', - }); - // fail('Should have thrown an error'); - } catch (error) { - // Uncomment this after the Deprecation DEPPS12 - // expect(error.code).toEqual(Parse.Error.INVALID_QUERY); - // expect(error.message).toEqual('Using the explain query parameter without the master key'); - } + it_id('b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e')(it_only_db('mongo'))( + 'explain requires master key when allowPublicExplain is false', + async () => { + console.log("just before test") + await reconfigureServer({ + databaseURI: 'mongodb://localhost:27017/parse', + databaseAdapter: null, + databaseOptions: { + allowPublicExplain: false, + }, + }); + + const obj = new TestObject({ foo: 'bar' }); + await obj.save(); + + // Without master key + const query = new Parse.Query(TestObject); + query.explain(); + await expectAsync(query.find()).toBeRejectedWith( + new Parse.Error( + Parse.Error.INVALID_QUERY, + 'Using the explain query parameter requires the master key' + ) + ); - // Test that explain with master key works fine + // With master key const queryWithMasterKey = new Parse.Query(TestObject); queryWithMasterKey.explain(); const result = await queryWithMasterKey.find({ useMasterKey: true }); - - // Should return explain result (not throw error) expect(result).toBeDefined(); } ); + + it_id('c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f')(it_only_db('mongo'))( + 'explain works with and without master key by default (allowPublicExplain not set)', + async () => { + await reconfigureServer({}); + + const obj = new TestObject({ foo: 'bar' }); + await obj.save(); + + // Without master key (default behavior) + const query = new Parse.Query(TestObject); + query.explain(); + const resultWithoutMasterKey = await query.find(); + expect(resultWithoutMasterKey).toBeDefined(); + + // With master key + const queryWithMasterKey = new Parse.Query(TestObject); + queryWithMasterKey.explain(); + const resultWithMasterKey = await queryWithMasterKey.find({ useMasterKey: true }); + expect(resultWithMasterKey).toBeDefined(); + } + ); }); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 481d5257d9..0a8bc94744 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -154,8 +154,13 @@ export class MongoStorageAdapter implements StorageAdapter { this.enableSchemaHooks = !!mongoOptions.enableSchemaHooks; this.schemaCacheTtl = mongoOptions.schemaCacheTtl; this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation; - for (const key of ['enableSchemaHooks', 'schemaCacheTtl', 'maxTimeMS', 'disableIndexFieldValidation']) { - delete mongoOptions[key]; + for (const key of [ + 'enableSchemaHooks', + 'schemaCacheTtl', + 'maxTimeMS', + 'disableIndexFieldValidation', + 'allowPublicExplain', + ]) { delete this._mongoOptions[key]; } } diff --git a/src/Config.js b/src/Config.js index bf6d50626c..df2e404bc3 100644 --- a/src/Config.js +++ b/src/Config.js @@ -602,6 +602,11 @@ export class Config { } else if (typeof databaseOptions.schemaCacheTtl !== 'number') { throw `databaseOptions.schemaCacheTtl must be a number`; } + if (databaseOptions.allowPublicExplain === undefined) { + databaseOptions.allowPublicExplain = DatabaseOptions.allowPublicExplain.default; + } else if (typeof databaseOptions.allowPublicExplain !== 'boolean') { + throw `databaseOptions.allowPublicExplain must be a boolean`; + } } static validateRateLimit(rateLimit) { diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 970364432b..73e9ec9903 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -18,4 +18,10 @@ module.exports = [ { optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' }, { optionKey: 'enableInsecureAuthAdapters', changeNewDefault: 'false' }, + { + optionKey: 'databaseOptions.allowPublicExplain', + changeNewDefault: 'false', + solution: + 'To prepare for the future change, set Parse Server option databaseOptions.allowPublicExplain to false and ensure explain queries are only made with master key.', + }, ]; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 205c35fa77..596a84d9ff 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -1083,6 +1083,13 @@ module.exports.FileUploadOptions = { }, }; module.exports.DatabaseOptions = { + allowPublicExplain: { + env: 'PARSE_SERVER_DATABASE_ALLOW_PUBLIC_EXPLAIN', + help: + 'Set to `true` to allow explain queries without master key. This option is deprecated and the default will change to `false` in a future version.', + action: parsers.booleanParser, + default: true, + }, autoSelectFamily: { env: 'PARSE_SERVER_DATABASE_AUTO_SELECT_FAMILY', help: diff --git a/src/Options/docs.js b/src/Options/docs.js index dde5942500..25f2e87cfb 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -240,6 +240,7 @@ /** * @interface DatabaseOptions + * @property {Boolean} allowPublicExplain Set to `true` to allow explain queries without master key. This option is deprecated and the default will change to `false` in a future version. * @property {Boolean} autoSelectFamily The MongoDB driver option to set whether the socket attempts to connect to IPv6 and IPv4 addresses until a connection is established. If available, the driver will select the first IPv6 address. * @property {Number} autoSelectFamilyAttemptTimeout The MongoDB driver option to specify the amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the autoSelectFamily option. If set to a positive integer less than 10, the value 10 is used instead. * @property {Number} connectTimeoutMS The MongoDB driver option to specify the amount of time, in milliseconds, to wait to establish a single TCP socket connection to the server before raising an error. Specifying 0 disables the connection timeout. diff --git a/src/Options/index.js b/src/Options/index.js index ff8287b86b..014a35b2e8 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -634,6 +634,9 @@ export interface DatabaseOptions { autoSelectFamilyAttemptTimeout: ?number; /* Set to `true` to disable validation of index fields. When disabled, indexes can be created even if the fields do not exist in the schema. This can be useful when creating indexes on fields that will be added later. */ disableIndexFieldValidation: ?boolean; + /* Set to `true` to allow explain queries without master key. This option is deprecated and the default will change to `false` in a future version. + :DEFAULT: true */ + allowPublicExplain: ?boolean; } export interface AuthAdapter { diff --git a/src/rest.js b/src/rest.js index fc7027c4bb..e2e688a972 100644 --- a/src/rest.js +++ b/src/rest.js @@ -13,7 +13,6 @@ var RestQuery = require('./RestQuery'); var RestWrite = require('./RestWrite'); var triggers = require('./triggers'); const { enforceRoleSecurity } = require('./SharedRest'); -const Deprecator = require('./Deprecator/Deprecator'); function checkTriggers(className, config, types) { return types.some(triggerType => { @@ -37,16 +36,14 @@ async function runFindTriggers( const { isGet } = options; if (restOptions && restOptions.explain && !auth.isMaster) { - // After the Deprecation DEPPS12 uncomment this to throw an error - // throw new Parse.Error( - // Parse.Error.INVALID_QUERY, - // 'Using the explain query parameter without the master key' - // ); + const allowPublicExplain = config.databaseOptions?.allowPublicExplain ?? true; - // Deprecation DEPPS12 - Deprecator.logRuntimeDeprecation({ - usage: 'Using the explain query parameter without the master key', - }); + if (!allowPublicExplain) { + throw new Parse.Error( + Parse.Error.INVALID_QUERY, + 'Using the explain query parameter requires the master key' + ); + } } // Run beforeFind trigger - may modify query or return objects directly From 19ebc5bd09f06175dbd998ceca74252d2b841cc1 Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:30:56 +0100 Subject: [PATCH 5/5] feat: doc --- DEPRECATIONS.md | 2 +- changelogs/CHANGELOG_alpha.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 656f70f221..7861f2dd9e 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -15,7 +15,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | 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) | removed | - | | DEPPS10 | Encode `Parse.Object` in Cloud Function and remove option `encodeParseObjectInCloudFunction` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 9.0.0 (2026) | deprecated | - | | DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | deprecated | - | -| DEPPS12 | Restrict `explain` query parameter to master key | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | - | +| DEPPS12 | Database option `allowPublicExplain` defaults to `true` | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.3.0 (2025) | 9.0.0 (2027) | deprecated | - || 9.0.0 (2027) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_removal]: ## "The version and date of the planned removal." diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 5e5ecfbca3..6407a91f45 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -2,7 +2,7 @@ ### Features -- Deprecation DEPPS12: `explain` query parameter without master key ([#7519](https://github.com/parse-community/parse-server/issues/7519)) ([DEPPS12](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md#depps12)) +- Deprecation DEPPS12: database option `allowPublicExplain` defaults to `true` ([#7519](https://github.com/parse-community/parse-server/issues/7519)) ([DEPPS12](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md#depps12)) # [8.3.0-alpha.12](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.11...8.3.0-alpha.12) (2025-10-25)