From b3924bd36b3c6e04c38a2db86658c1985bb4b691 Mon Sep 17 00:00:00 2001 From: vivekjoshi556 Date: Tue, 5 Mar 2024 02:16:43 +0530 Subject: [PATCH 01/22] feat: Log warning in case of a unknown options --- spec/ParseConfigKey.spec.js | 93 +++++++++++++++++++++++++++++++++++++ src/Config.js | 89 ++++++++++++++++++++++++----------- src/ParseServer.js | 4 ++ 3 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 spec/ParseConfigKey.spec.js diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js new file mode 100644 index 0000000000..9041d1f52b --- /dev/null +++ b/spec/ParseConfigKey.spec.js @@ -0,0 +1,93 @@ +const ParseServer = require('../lib/index').ParseServer; +const controllers = require('../lib/Controllers/'); +const Config = require('../lib/Config'); + +describe('Config Keys', () => { + it('should throw a warning for incorrect key name in Config', () => { + const doNothing = () => { + return {}; + }; + + spyOn(controllers, 'getControllers').and.callFake(doNothing); + spyOn(Config, 'put').and.callFake(doNothing); + // Spy on the console.warn method to capture warnings + spyOn(console, 'warn'); + + new ParseServer({ + ...defaultConfiguration, + unknownKeyName: 'unknownValue', // invalid key + masterKeyIPs: '', // invalid key + accountLockout: { + duration: 10, + threshold: 10, + UnlockOnPasswordReset: false, // invalid key + }, + passwordPolicy: { + MaxPasswordAge: 10, // invalid key + }, + fileUpload: { + EnableForAnonymousUser: false, // invalid key + }, + schema: { + Strict: true, // invalid key + }, + rateLimit: { + requestPath: '', + requestTimeWindow: 10, + requestCount: 10, + IncludeInternalRequests: false, // invalid key + }, + security: { + EnableCheck: true, // invalid key + }, + pages: { + EnableRouter: true, // invalid key + }, + idempotencyOptions: { + Ttl: 10, // invalid key + }, + databaseOptions: { + SchemaCacheTtl: 10, // invalid key + }, + logLevels: { + CloudFunctionError: 'error', // invalid key + }, + }); + + // Check if console.warn was called with the expected message + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: unknownKeyName' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: masterKeyIPs' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: UnlockOnPasswordReset' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: MaxPasswordAge' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: EnableForAnonymousUser' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: Strict' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: IncludeInternalRequests' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: EnableCheck' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: EnableRouter' + ); + expect(console.warn).toHaveBeenCalledWith('Warning: The following key is not recognized: Ttl'); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: SchemaCacheTtl' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key is not recognized: CloudFunctionError' + ); + }); +}); diff --git a/src/Config.js b/src/Config.js index 0e8cdda246..e9bb559b1f 100644 --- a/src/Config.js +++ b/src/Config.js @@ -18,6 +18,8 @@ import { ParseServerOptions, SchemaOptions, SecurityOptions, + PasswordPolicyOptions, + RateLimitOptions, } from './Options/Definitions'; import ParseServer from './cloud-code/Parse.Server'; @@ -63,34 +65,45 @@ export class Config { return serverConfiguration; } - static validateOptions({ - publicServerURL, - revokeSessionOnPasswordReset, - expireInactiveSessions, - sessionLength, - defaultLimit, - maxLimit, - accountLockout, - passwordPolicy, - masterKeyIps, - masterKey, - maintenanceKey, - maintenanceKeyIps, - readOnlyMasterKey, - allowHeaders, - idempotencyOptions, - fileUpload, - pages, - security, - enforcePrivateUsers, - schema, - requestKeywordDenylist, - allowExpiredAuthDataToken, - logLevels, - rateLimit, - databaseOptions, - extendSessionOnUse, - }) { + static validateConfigKeyNames(actual, given) { + for (const key of given) { + if (!actual.includes(key)) { + console.warn(`Warning: The following key is not recognized: ${key}`); + } + } + } + + static validateOptions(options) { + const { + publicServerURL, + revokeSessionOnPasswordReset, + expireInactiveSessions, + sessionLength, + defaultLimit, + maxLimit, + accountLockout, + passwordPolicy, + masterKeyIps, + masterKey, + maintenanceKey, + maintenanceKeyIps, + readOnlyMasterKey, + allowHeaders, + idempotencyOptions, + fileUpload, + pages, + security, + enforcePrivateUsers, + schema, + requestKeywordDenylist, + allowExpiredAuthDataToken, + logLevels, + rateLimit, + databaseOptions, + extendSessionOnUse, + } = options; + + Config.validateConfigKeyNames(Object.keys(ParseServerOptions), Object.keys(options)); if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); } @@ -178,6 +191,7 @@ export class Config { if (Object.prototype.toString.call(security) !== '[object Object]') { throw 'Parse Server option security must be an object.'; } + Config.validateConfigKeyNames(Object.keys(SecurityOptions), Object.keys(security)); if (security.enableCheck === undefined) { security.enableCheck = SecurityOptions.enableCheck.default; } else if (!isBoolean(security.enableCheck)) { @@ -192,6 +206,8 @@ export class Config { static validateSchemaOptions(schema: SchemaOptions) { if (!schema) return; + + Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema)); if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } @@ -236,6 +252,8 @@ export class Config { if (Object.prototype.toString.call(pages) !== '[object Object]') { throw 'Parse Server option pages must be an object.'; } + + Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages)); if (pages.enableRouter === undefined) { pages.enableRouter = PagesOptions.enableRouter.default; } else if (!isBoolean(pages.enableRouter)) { @@ -295,6 +313,7 @@ export class Config { if (!idempotencyOptions) { return; } + Config.validateConfigKeyNames(Object.keys(IdempotencyOptions), Object.keys(idempotencyOptions)); if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { @@ -311,6 +330,10 @@ export class Config { static validateAccountLockoutPolicy(accountLockout) { if (accountLockout) { + Config.validateConfigKeyNames( + Object.keys(AccountLockoutOptions), + Object.keys(accountLockout) + ); if ( typeof accountLockout.duration !== 'number' || accountLockout.duration <= 0 || @@ -337,6 +360,10 @@ export class Config { static validatePasswordPolicy(passwordPolicy) { if (passwordPolicy) { + Config.validateConfigKeyNames( + Object.keys(PasswordPolicyOptions), + Object.keys(passwordPolicy) + ); if ( passwordPolicy.maxPasswordAge !== undefined && (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) @@ -453,6 +480,8 @@ export class Config { } throw e; } + + Config.validateConfigKeyNames(Object.keys(FileUploadOptions), Object.keys(fileUpload)); if (fileUpload.enableForAnonymousUser === undefined) { fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { @@ -543,6 +572,7 @@ export class Config { } static validateLogLevels(logLevels) { + Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels)); for (const key of Object.keys(LogLevels)) { if (logLevels[key]) { if (validLogLevels.indexOf(logLevels[key]) === -1) { @@ -558,6 +588,8 @@ export class Config { if (databaseOptions == undefined) { return; } + + Config.validateConfigKeyNames(Object.keys(DatabaseOptions), Object.keys(databaseOptions)); if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') { throw `databaseOptions must be an object`; } @@ -583,6 +615,7 @@ export class Config { ) { throw `rateLimit must be an array or object`; } + Config.validateConfigKeyNames(Object.keys(RateLimitOptions), Object.keys(rateLimit)); const options = Array.isArray(rateLimit) ? rateLimit : [rateLimit]; for (const option of options) { if (Object.prototype.toString.call(option) !== '[object Object]') { diff --git a/src/ParseServer.js b/src/ParseServer.js index 880c5f84aa..580189d845 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,6 +45,7 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; +import { ParseServerOptions as ParseServerOptionDef } from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -59,6 +60,9 @@ class ParseServer { constructor(options: ParseServerOptions) { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); + + Config.validateConfigKeyNames(Object.keys(ParseServerOptionDef), Object.keys(options)); + // Set option defaults injectDefaults(options); const { From 99a03d2d6d25bbc6b596da28936eb4f7e727831c Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Thu, 14 Mar 2024 02:39:38 +0000 Subject: [PATCH 02/22] fix: splitting test cases | ignored internal configs | covered all config variables | fixed issues with initial validations --- spec/ParseConfigKey.spec.js | 327 +++++++++++++++++++++++++++++------- src/Config.js | 124 ++++++++++++-- src/Controllers/index.js | 11 ++ src/ParseServer.js | 32 +++- 4 files changed, 419 insertions(+), 75 deletions(-) diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js index 9041d1f52b..c86a9cef46 100644 --- a/spec/ParseConfigKey.spec.js +++ b/spec/ParseConfigKey.spec.js @@ -1,93 +1,302 @@ const ParseServer = require('../lib/index').ParseServer; -const controllers = require('../lib/Controllers/'); const Config = require('../lib/Config'); +const Deprecator = require('../lib/Deprecator/Deprecator'); + +const doNothing = () => {}; describe('Config Keys', () => { - it('should throw a warning for incorrect key name in Config', () => { - const doNothing = () => { + beforeEach(() => { + spyOn(Deprecator, 'scanParseServerOptions').and.callFake(doNothing); + spyOn(Config, 'put').and.callFake(() => { return {}; - }; - - spyOn(controllers, 'getControllers').and.callFake(doNothing); - spyOn(Config, 'put').and.callFake(doNothing); + }); // Spy on the console.warn method to capture warnings spyOn(console, 'warn'); + }); - new ParseServer({ - ...defaultConfiguration, - unknownKeyName: 'unknownValue', // invalid key - masterKeyIPs: '', // invalid key - accountLockout: { - duration: 10, - threshold: 10, - UnlockOnPasswordReset: false, // invalid key - }, - passwordPolicy: { - MaxPasswordAge: 10, // invalid key - }, - fileUpload: { - EnableForAnonymousUser: false, // invalid key - }, - schema: { - Strict: true, // invalid key - }, - rateLimit: { - requestPath: '', - requestTimeWindow: 10, - requestCount: 10, - IncludeInternalRequests: false, // invalid key - }, - security: { - EnableCheck: true, // invalid key - }, - pages: { - EnableRouter: true, // invalid key - }, - idempotencyOptions: { - Ttl: 10, // invalid key - }, - databaseOptions: { - SchemaCacheTtl: 10, // invalid key - }, - logLevels: { - CloudFunctionError: 'error', // invalid key - }, - }); + it('should run fine', () => { + try { + new ParseServer({ + ...defaultConfiguration, + }); + } catch (err) { + fail('Should run without error'); + } + }); + + it('should throw 2 warnings for incorrect key names in Config', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + unknownKeyName: 'unknownValue', // invalid key + masterKeyIPs: '', // invalid key + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from ParseServerOptions is not recognized: masterKeyIPs' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' + ); + }); + + it('should throw incorrect key warning for Schema Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + schema: { + Strict: true, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from SchemaOptions is not recognized: Strict' + ); + }); + + it('should throw incorrect key warning for ParseServer Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + unknownKeyName: 'unknownValue', // invalid key + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); // Check if console.warn was called with the expected message expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: unknownKeyName' + 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' + ); + }); + + it('should throw incorrect key warning for RateLimitOption Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + rateLimit: { + requestPath: '', + requestTimeWindow: 10, + requestCount: 10, + IncludeInternalRequests: false, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from RateLimitOptions is not recognized: IncludeInternalRequests' + ); + }); + + it('should throw incorrect key warning for Security Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + security: { + EnableCheck: true, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from SecurityOptions is not recognized: EnableCheck' + ); + }); + + it('should throw incorrect key warning for Pages Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + pages: { + EnableRouter: true, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from PagesOptions is not recognized: EnableRouter' ); + }); + + it('should throw incorrect key warning for PagesRoute Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + pages: { + customRoutes: [ + { + Handler: () => {}, // invalid key + }, + ], + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: masterKeyIPs' + 'Warning: The following key from PagesRoute is not recognized: Handler' ); + }); + + it('should throw incorrect key warning for PagesCustomUrls Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + pages: { + customUrls: { + PasswordReset: '', // invalid key + }, + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: UnlockOnPasswordReset' + 'Warning: The following key from PagesCustomUrlsOptions is not recognized: PasswordReset' ); + }); + + it('should throw incorrect key warning for customPagesOption Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + customPages: { + InvalidLink: '', // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: MaxPasswordAge' + 'Warning: The following key from CustomPagesOptions is not recognized: InvalidLink' ); + }); + + it('should throw incorrect key warning for LiveQueryOptions Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + liveQuery: { + ClassNames: '', // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: EnableForAnonymousUser' + 'Warning: The following key from LiveQueryOptions is not recognized: ClassNames' ); + }); + + it('should throw incorrect key warning for IdempotencyOptions Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + idempotencyOptions: { + Ttl: 10, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: Strict' + 'Warning: The following key from IdempotencyOptions is not recognized: Ttl' ); + }); + + it('should throw incorrect key warning for AccountLockoutOptions Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + accountLockout: { + duration: 10, + threshold: 10, + UnlockOnPasswordReset: false, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: IncludeInternalRequests' + 'Warning: The following key from AccountLockoutOptions is not recognized: UnlockOnPasswordReset' ); + }); + + it('should throw incorrect key warning for PasswordPolicyOptions Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + passwordPolicy: { + MaxPasswordAge: 10, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: EnableCheck' + 'Warning: The following key from PasswordPolicyOptions is not recognized: MaxPasswordAge' ); + }); + + it('should throw incorrect key warning for FileUploadOptions Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + fileUpload: { + EnableForAnonymousUser: false, // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: EnableRouter' + 'Warning: The following key from FileUploadOptions is not recognized: EnableForAnonymousUser' ); - expect(console.warn).toHaveBeenCalledWith('Warning: The following key is not recognized: Ttl'); + }); + + it('should throw incorrect key warning for DatabaseOptions Config key', () => { + const dbConfig = { + ...defaultConfiguration, + databaseOptions: { + SchemaCacheTtl: 10, // invalid key + }, + }; + delete dbConfig.databaseAdapter; + + expect(() => { + new ParseServer(dbConfig); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: SchemaCacheTtl' + 'Warning: The following key from DatabaseOptions is not recognized: SchemaCacheTtl' ); + }); + + it('should throw incorrect key warning for LogLevels Config key', () => { + expect(() => { + new ParseServer({ + ...defaultConfiguration, + logLevels: { + CloudFunctionError: 'error', // invalid key + }, + }); + }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); expect(console.warn).toHaveBeenCalledWith( - 'Warning: The following key is not recognized: CloudFunctionError' + 'Warning: The following key from LogLevels is not recognized: CloudFunctionError' ); }); }); + +describe('Config Keys Checked on Server Start', () => { + it('should throw incorrect key warning for LiveQueryServerOptions Config key', async () => { + spyOn(Deprecator, 'scanParseServerOptions').and.callFake(doNothing); + // Spy on the console.warn method to capture warnings + spyOn(console, 'warn'); + + try { + await ParseServer.startApp({ + ...defaultConfiguration, + liveQueryServerOptions: { + MasterKey: '', // invalid key + }, + }); + + fail('Expected an error to be thrown'); + } catch (err) { + expect(console.warn).toHaveBeenCalledWith( + 'Warning: The following key from LiveQueryServerOptions is not recognized: MasterKey' + ); + + expect(err.message).toBe( + 'Some Unknown Keys Found in Configuration. See warning messages above.' + ); + } + }); +}); diff --git a/src/Config.js b/src/Config.js index e9bb559b1f..e231af10a8 100644 --- a/src/Config.js +++ b/src/Config.js @@ -10,6 +10,7 @@ import { logLevels as validLogLevels } from './Controllers/LoggerController'; import { version } from '../package.json'; import { AccountLockoutOptions, + PagesRoute, DatabaseOptions, FileUploadOptions, IdempotencyOptions, @@ -20,6 +21,8 @@ import { SecurityOptions, PasswordPolicyOptions, RateLimitOptions, + CustomPagesOptions, + PagesCustomUrlsOptions, } from './Options/Definitions'; import ParseServer from './cloud-code/Parse.Server'; @@ -65,16 +68,61 @@ export class Config { return serverConfiguration; } - static validateConfigKeyNames(actual, given) { + static validateConfigKeyNames(actual, given, parent) { + const internalConfigVars = [ + 'level', + 'state', + 'loggerController', + 'filesController', + 'userController', + 'pushController', + 'hasPushScheduledSupport', + 'hasPushSupport', + 'pushWorker', + 'pushControllerQueue', + 'analyticsController', + 'cacheController', + 'parseGraphQLController', + 'liveQueryController', + 'databaseController', + 'hooksController', + 'authDataManager', + 'schemaCache', + 'masterKeyIpsStore', + 'maintenanceKeyIpsStore', + 'failedConfigKeyVerification', + 'patternValidator', + + 'applicationId', + 'database', + '_mount', + 'generateSessionExpiresAt', + 'generateEmailVerifyTokenExpiresAt', + 'version', + 'RateLimitZone', + + // From Test files + 'retryWrites', + 'customIdSize', + 'path', + 'exampleKey', + 'websocketTimeout', + 'ops', + ]; + for (const key of given) { + if (internalConfigVars.includes(key)) continue; + if (!actual.includes(key)) { - console.warn(`Warning: The following key is not recognized: ${key}`); + Config.failedConfigKeyVerification = true; + console.warn(`Warning: The following key from ${parent} is not recognized: ${key}`); } } } static validateOptions(options) { const { + customPages, publicServerURL, revokeSessionOnPasswordReset, expireInactiveSessions, @@ -103,7 +151,6 @@ export class Config { extendSessionOnUse, } = options; - Config.validateConfigKeyNames(Object.keys(ParseServerOptions), Object.keys(options)); if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); } @@ -145,6 +192,20 @@ export class Config { this.validateRateLimit(rateLimit); this.validateLogLevels(logLevels); this.validateDatabaseOptions(databaseOptions); + this.validateCustomPages(customPages); + } + + static validateCustomPages(customPages) { + if (!customPages) return; + + if (Object.prototype.toString.call(customPages) !== '[object Object]') { + throw Error('Parse Server option customPages must be an object.'); + } + Config.validateConfigKeyNames( + Object.keys(CustomPagesOptions), + Object.keys(customPages), + 'CustomPagesOptions' + ); } static validateControllers({ @@ -191,7 +252,11 @@ export class Config { if (Object.prototype.toString.call(security) !== '[object Object]') { throw 'Parse Server option security must be an object.'; } - Config.validateConfigKeyNames(Object.keys(SecurityOptions), Object.keys(security)); + Config.validateConfigKeyNames( + Object.keys(SecurityOptions), + Object.keys(security), + 'SecurityOptions' + ); if (security.enableCheck === undefined) { security.enableCheck = SecurityOptions.enableCheck.default; } else if (!isBoolean(security.enableCheck)) { @@ -207,7 +272,7 @@ export class Config { static validateSchemaOptions(schema: SchemaOptions) { if (!schema) return; - Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema)); + Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'SchemaOptions'); if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } @@ -253,7 +318,7 @@ export class Config { throw 'Parse Server option pages must be an object.'; } - Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages)); + Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages), 'PagesOptions'); if (pages.enableRouter === undefined) { pages.enableRouter = PagesOptions.enableRouter.default; } else if (!isBoolean(pages.enableRouter)) { @@ -301,11 +366,22 @@ export class Config { pages.customUrls = PagesOptions.customUrls.default; } else if (Object.prototype.toString.call(pages.customUrls) !== '[object Object]') { throw 'Parse Server option pages.customUrls must be an object.'; + } else { + Config.validateConfigKeyNames( + Object.keys(PagesCustomUrlsOptions), + Object.keys(pages.customUrls), + 'PagesCustomUrlsOptions' + ); } + if (pages.customRoutes === undefined) { pages.customRoutes = PagesOptions.customRoutes.default; } else if (!(pages.customRoutes instanceof Array)) { throw 'Parse Server option pages.customRoutes must be an array.'; + } else { + pages.customRoutes.forEach(item => { + Config.validateConfigKeyNames(Object.keys(PagesRoute), Object.keys(item), 'PagesRoute'); + }); } } @@ -313,7 +389,11 @@ export class Config { if (!idempotencyOptions) { return; } - Config.validateConfigKeyNames(Object.keys(IdempotencyOptions), Object.keys(idempotencyOptions)); + Config.validateConfigKeyNames( + Object.keys(IdempotencyOptions), + Object.keys(idempotencyOptions), + 'IdempotencyOptions' + ); if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { @@ -332,7 +412,8 @@ export class Config { if (accountLockout) { Config.validateConfigKeyNames( Object.keys(AccountLockoutOptions), - Object.keys(accountLockout) + Object.keys(accountLockout), + 'AccountLockoutOptions' ); if ( typeof accountLockout.duration !== 'number' || @@ -362,7 +443,8 @@ export class Config { if (passwordPolicy) { Config.validateConfigKeyNames( Object.keys(PasswordPolicyOptions), - Object.keys(passwordPolicy) + Object.keys(passwordPolicy), + 'PasswordPolicyOptions' ); if ( passwordPolicy.maxPasswordAge !== undefined && @@ -481,7 +563,11 @@ export class Config { throw e; } - Config.validateConfigKeyNames(Object.keys(FileUploadOptions), Object.keys(fileUpload)); + Config.validateConfigKeyNames( + Object.keys(FileUploadOptions), + Object.keys(fileUpload), + 'FileUploadOptions' + ); if (fileUpload.enableForAnonymousUser === undefined) { fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { @@ -572,7 +658,7 @@ export class Config { } static validateLogLevels(logLevels) { - Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels)); + Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels), 'LogLevels'); for (const key of Object.keys(LogLevels)) { if (logLevels[key]) { if (validLogLevels.indexOf(logLevels[key]) === -1) { @@ -589,10 +675,15 @@ export class Config { return; } - Config.validateConfigKeyNames(Object.keys(DatabaseOptions), Object.keys(databaseOptions)); if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') { throw `databaseOptions must be an object`; } + Config.validateConfigKeyNames( + Object.keys(DatabaseOptions), + Object.keys(databaseOptions), + 'DatabaseOptions' + ); + if (databaseOptions.enableSchemaHooks === undefined) { databaseOptions.enableSchemaHooks = DatabaseOptions.enableSchemaHooks.default; } else if (typeof databaseOptions.enableSchemaHooks !== 'boolean') { @@ -615,12 +706,19 @@ export class Config { ) { throw `rateLimit must be an array or object`; } - Config.validateConfigKeyNames(Object.keys(RateLimitOptions), Object.keys(rateLimit)); + const options = Array.isArray(rateLimit) ? rateLimit : [rateLimit]; for (const option of options) { if (Object.prototype.toString.call(option) !== '[object Object]') { throw `rateLimit must be an array of objects`; } + + Config.validateConfigKeyNames( + Object.keys(RateLimitOptions), + Object.keys(option), + 'RateLimitOptions' + ); + if (option.requestPath == null) { throw `rateLimit.requestPath must be defined`; } diff --git a/src/Controllers/index.js b/src/Controllers/index.js index 0a9b3db57d..cf5f8ba16b 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -1,5 +1,6 @@ import authDataManager from '../Adapters/Auth'; import { ParseServerOptions } from '../Options'; +import { LiveQueryOptions } from '../Options/Definitions'; import { loadAdapter } from '../Adapters/AdapterLoader'; import defaults from '../defaults'; // Controllers @@ -25,6 +26,7 @@ import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorage import ParsePushAdapter from '@parse/push-adapter'; import ParseGraphQLController from './ParseGraphQLController'; import SchemaCache from '../Adapters/Cache/SchemaCache'; +import Config from '../Config'; export function getControllers(options: ParseServerOptions) { const loggerController = getLoggerController(options); @@ -146,6 +148,15 @@ export function getAnalyticsController(options: ParseServerOptions): AnalyticsCo } export function getLiveQueryController(options: ParseServerOptions): LiveQueryController { + if (options.liveQuery) { + if (Object.prototype.toString.call(options.liveQuery) === '[object Object]') { + Config.validateConfigKeyNames( + Object.keys(LiveQueryOptions), + Object.keys(options.liveQuery), + 'LiveQueryOptions' + ); + } + } return new LiveQueryController(options.liveQuery); } diff --git a/src/ParseServer.js b/src/ParseServer.js index 580189d845..0fa78a7d45 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,7 +45,10 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; -import { ParseServerOptions as ParseServerOptionDef } from './Options/Definitions'; +import { + ParseServerOptions as ParseServerOptionDef, + LiveQueryServerOptions as LiveQueryServerOptionsDef, +} from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -61,7 +64,11 @@ class ParseServer { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); - Config.validateConfigKeyNames(Object.keys(ParseServerOptionDef), Object.keys(options)); + Config.validateConfigKeyNames( + Object.keys(ParseServerOptionDef), + Object.keys(options), + 'ParseServerOptions' + ); // Set option defaults injectDefaults(options); @@ -74,9 +81,13 @@ class ParseServer { // Initialize the node client SDK automatically Parse.initialize(appId, javascriptKey || 'unused', masterKey); Parse.serverURL = serverURL; - Config.validateOptions(options); const allControllers = controllers.getControllers(options); + if (Config.failedConfigKeyVerification) { + delete Config.failedConfigKeyVerification; + throw new Error('Some Unknown Keys Found in Configuration. See warning messages above.'); + } + options.state = 'initialized'; this.config = Config.put(Object.assign({}, options, allControllers)); this.config.masterKeyIpsStore = new Map(); @@ -404,6 +415,21 @@ class ParseServer { config: LiveQueryServerOptions, options: ParseServerOptions ) { + if (config) { + if (Object.prototype.toString.call(config) === '[object Object]') { + Config.validateConfigKeyNames( + Object.keys(LiveQueryServerOptionsDef), + Object.keys(config), + 'LiveQueryServerOptions' + ); + + if (Config.failedConfigKeyVerification) { + delete Config.failedConfigKeyVerification; + throw new Error('Some Unknown Keys Found in Configuration. See warning messages above.'); + } + } + } + if (!httpServer || (config && config.port)) { var app = express(); httpServer = require('http').createServer(app); From b70592e43b8e2ed202e384cfbcec93873047d2eb Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 16 Mar 2024 15:39:57 +0000 Subject: [PATCH 03/22] doc: included documentation for using this feature --- CONTRIBUTING.md | 4 +++- src/Config.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 270b66e385..f9afa593da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,7 +407,9 @@ Introducing new [Parse Server configuration][config] parameters requires the fol For example, take a look at the existing Parse Server `security` parameter. It is a parameter group, because it has multiple sub-parameter such as `checkGroups`. Its interface is defined in [index.js][config-index] as `export interface SecurityOptions`. Therefore, the value to add to `nestedOptionTypes` would be `SecurityOptions`, the value to add to `nestedOptionEnvPrefix` would be `PARSE_SERVER_SECURITY_`. 3. Execute `npm run definitions` to automatically create the definitions in [/src/Options/Definitions.js][config-def] and [/src/Options/docs.js][config-docs]. -4. Add parameter value validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js). +4. Add appropriate validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js). For reference checkout other validation methods in same file. + - For key name validation you must use `validateConfigKeyNames` method. + - Perform appropriate parameter value validation. 5. Add test cases to ensure the correct parameter value validation. Parse Server throws an error at launch if an invalid value is set for any configuration parameter. 6. Execute `npm run docs` to generate the documentation in the `/out` directory. Take a look at the documentation whether the description and formatting of the newly introduced parameters is satisfactory. diff --git a/src/Config.js b/src/Config.js index 3244217b90..5796cbb6ea 100644 --- a/src/Config.js +++ b/src/Config.js @@ -280,10 +280,11 @@ export class Config { static validateSchemaOptions(schema: SchemaOptions) { if (!schema) return; - Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'SchemaOptions'); if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } + Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'SchemaOptions'); + if (schema.definitions === undefined) { schema.definitions = SchemaOptions.definitions.default; } else if (!Array.isArray(schema.definitions)) { From d4ab4f012e26a116d747283b4640d2e8c82d645c Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:14:55 +0530 Subject: [PATCH 04/22] Apply suggestions from code review Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9afa593da..d55ef9f8bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,9 +407,10 @@ Introducing new [Parse Server configuration][config] parameters requires the fol For example, take a look at the existing Parse Server `security` parameter. It is a parameter group, because it has multiple sub-parameter such as `checkGroups`. Its interface is defined in [index.js][config-index] as `export interface SecurityOptions`. Therefore, the value to add to `nestedOptionTypes` would be `SecurityOptions`, the value to add to `nestedOptionEnvPrefix` would be `PARSE_SERVER_SECURITY_`. 3. Execute `npm run definitions` to automatically create the definitions in [/src/Options/Definitions.js][config-def] and [/src/Options/docs.js][config-docs]. -4. Add appropriate validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js). For reference checkout other validation methods in same file. - - For key name validation you must use `validateConfigKeyNames` method. - - Perform appropriate parameter value validation. +4. Add parameter validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js): + - Validate parameter name by using `validateConfigKeyNames`. + - Validate parameter value by checking the value type and the value itself (e.g. min, max bounds for numeric types, regex parsing for string types, or object parsing for object types). + For reference see existing parameter validations. 5. Add test cases to ensure the correct parameter value validation. Parse Server throws an error at launch if an invalid value is set for any configuration parameter. 6. Execute `npm run docs` to generate the documentation in the `/out` directory. Take a look at the documentation whether the description and formatting of the newly introduced parameters is satisfactory. From e9311b380f634d8e3bb111b0c5857e78e174f9bb Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Mar 2024 03:46:06 +1100 Subject: [PATCH 05/22] fix: CacheAdapter does not connect when using a CacheAdapter with a JSON config (#8633) --- spec/RedisCacheAdapter.spec.js | 14 ++++++++++++++ src/ParseServer.js | 9 ++++++--- src/TestUtils.js | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/spec/RedisCacheAdapter.spec.js b/spec/RedisCacheAdapter.spec.js index 3fd6f32dde..9b88e857c4 100644 --- a/spec/RedisCacheAdapter.spec.js +++ b/spec/RedisCacheAdapter.spec.js @@ -167,4 +167,18 @@ describe_only(() => { .then(() => expect(getQueueCount(cache)).toEqual(0)) .then(done); }); + + it('should start and connect cache adapter', async () => { + const server = await reconfigureServer({ + cacheAdapter: { + module: `${__dirname.replace('/spec', '')}/lib/Adapters/Cache/RedisCacheAdapter`, + options: { + url: 'redis://127.0.0.1:6379/1', + }, + }, + }); + const symbol = Object.getOwnPropertySymbols(server.config.cacheController); + const client = server.config.cacheController[symbol[0]].client; + expect(client.isOpen).toBeTrue(); + }); }); diff --git a/src/ParseServer.js b/src/ParseServer.js index 0fa78a7d45..8da8f03c4c 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -109,10 +109,10 @@ class ParseServer { const { databaseController, hooksController, + cacheController, cloud, security, schema, - cacheAdapter, liveQueryController, } = this.config; try { @@ -127,8 +127,11 @@ class ParseServer { if (schema) { startupPromises.push(new DefinedSchemas(schema, this.config).execute()); } - if (cacheAdapter?.connect && typeof cacheAdapter.connect === 'function') { - startupPromises.push(cacheAdapter.connect()); + if ( + cacheController.adapter?.connect && + typeof cacheController.adapter.connect === 'function' + ) { + startupPromises.push(cacheController.adapter.connect()); } startupPromises.push(liveQueryController.connect()); await Promise.all(startupPromises); diff --git a/src/TestUtils.js b/src/TestUtils.js index 3d5133556d..6d4cfb0f14 100644 --- a/src/TestUtils.js +++ b/src/TestUtils.js @@ -13,7 +13,7 @@ export function destroyAllDataPermanently(fast) { Object.keys(AppCache.cache).map(appId => { const app = AppCache.get(appId); const deletePromises = []; - if (app.cacheAdapter) { + if (app.cacheAdapter && app.cacheAdapter.clear) { deletePromises.push(app.cacheAdapter.clear()); } if (app.databaseController) { From 8aaa1721c44dcf1004f4730359d5dd0af932e408 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 15 Mar 2024 16:46:57 +0000 Subject: [PATCH 06/22] chore(release): 7.0.0-alpha.27 [skip ci] # [7.0.0-alpha.27](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.26...7.0.0-alpha.27) (2024-03-15) ### Bug Fixes * CacheAdapter does not connect when using a CacheAdapter with a JSON config ([#8633](https://github.com/parse-community/parse-server/issues/8633)) ([720d24e](https://github.com/parse-community/parse-server/commit/720d24e18540da35d50957f17be878316ec30318)) --- 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 72f0e65efb..ebe3ddae68 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [7.0.0-alpha.27](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.26...7.0.0-alpha.27) (2024-03-15) + + +### Bug Fixes + +* CacheAdapter does not connect when using a CacheAdapter with a JSON config ([#8633](https://github.com/parse-community/parse-server/issues/8633)) ([720d24e](https://github.com/parse-community/parse-server/commit/720d24e18540da35d50957f17be878316ec30318)) + # [7.0.0-alpha.26](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.25...7.0.0-alpha.26) (2024-03-10) diff --git a/package-lock.json b/package-lock.json index 4d9b732e3a..88832836c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-server", - "version": "7.0.0-alpha.26", + "version": "7.0.0-alpha.27", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "7.0.0-alpha.26", + "version": "7.0.0-alpha.27", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 2d4989cf15..44eeaf8104 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "7.0.0-alpha.26", + "version": "7.0.0-alpha.27", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { From 8ef8f5412c1daf8173616d74ad6b695ffb2833ea Mon Sep 17 00:00:00 2001 From: Oussama Meglali <56823121+Meglali20@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:53:25 +0100 Subject: [PATCH 07/22] test: Fix flaky tests for comment in MongoDB query (#9015) --- spec/ParseQuery.Comment.spec.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/spec/ParseQuery.Comment.spec.js b/spec/ParseQuery.Comment.spec.js index c61c8c09c4..12713387a0 100644 --- a/spec/ParseQuery.Comment.spec.js +++ b/spec/ParseQuery.Comment.spec.js @@ -1,7 +1,6 @@ 'use strict'; const Config = require('../lib/Config'); -const TestUtils = require('../lib/TestUtils'); const { MongoClient } = require('mongodb'); const databaseURI = 'mongodb://localhost:27017/'; const request = require('../lib/request'); @@ -20,22 +19,33 @@ const masterKeyOptions = { json: true, }; +const profileLevel = 2; describe_only_db('mongo')('Parse.Query with comment testing', () => { - beforeEach(async () => { + beforeAll(async () => { config = Config.get('test'); client = await MongoClient.connect(databaseURI, { useNewUrlParser: true, useUnifiedTopology: true, }); database = client.db('parseServerMongoAdapterTestDatabase'); - const level = 2; - const profiling = await database.command({ profile: level }); - console.log(`profiling ${JSON.stringify(profiling)}`); + let profiler = await database.command({ profile: 0 }); + expect(profiler.was).toEqual(0); + console.log(`Disabling profiler : ${profiler.was}`); + profiler = await database.command({ profile: profileLevel }); + profiler = await database.command({ profile: -1 }); + console.log(`Enabling profiler : ${profiler.was}`); + profiler = await database.command({ profile: -1 }); + expect(profiler.was).toEqual(profileLevel); + }); + + beforeEach(async () => { + const profiler = await database.command({ profile: -1 }); + expect(profiler.was).toEqual(profileLevel); }); - afterEach(async () => { + afterAll(async () => { + await database.command({ profile: 0 }); await client.close(); - await TestUtils.destroyAllDataPermanently(false); }); it('send comment with query through REST', async () => { From 80fb070054ba20cee46e6756a819d8c121970412 Mon Sep 17 00:00:00 2001 From: Parse Platform <90459499+parseplatformorg@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:09:28 +0100 Subject: [PATCH 08/22] refactor: Security upgrade follow-redirects from 1.15.5 to 1.15.6 (#9017) --- 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 88832836c9..2c72c3fee3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "deepcopy": "2.1.0", "express": "4.18.2", "express-rate-limit": "6.11.2", - "follow-redirects": "1.15.5", + "follow-redirects": "^1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", "graphql-relay": "0.10.0", @@ -8214,9 +8214,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -25816,9 +25816,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "foreground-child": { "version": "2.0.0", diff --git a/package.json b/package.json index 44eeaf8104..0e0414afd6 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "deepcopy": "2.1.0", "express": "4.18.2", "express-rate-limit": "6.11.2", - "follow-redirects": "1.15.5", + "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", "graphql-relay": "0.10.0", From 79f41fcf3c4167e9b20cda7767c47fab9930f218 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:53:39 +0530 Subject: [PATCH 09/22] Update src/ParseServer.js Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> --- src/ParseServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseServer.js b/src/ParseServer.js index 8da8f03c4c..986a2f5a46 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -428,7 +428,7 @@ class ParseServer { if (Config.failedConfigKeyVerification) { delete Config.failedConfigKeyVerification; - throw new Error('Some Unknown Keys Found in Configuration. See warning messages above.'); + throw new Error('Unknown key(s) found in Parse Server configuration, see other warning messages for details.'); } } } From 93022fe9dadc2c0385fd39059ab35e76709591b9 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:14:09 +0000 Subject: [PATCH 10/22] fix: error message on server crash --- spec/ParseConfigKey.spec.js | 66 +++++++++++++++++++++++++++---------- src/ParseServer.js | 8 +++-- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js index c86a9cef46..4f0654f254 100644 --- a/spec/ParseConfigKey.spec.js +++ b/spec/ParseConfigKey.spec.js @@ -31,7 +31,9 @@ describe('Config Keys', () => { unknownKeyName: 'unknownValue', // invalid key masterKeyIPs: '', // invalid key }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from ParseServerOptions is not recognized: masterKeyIPs' ); @@ -48,7 +50,9 @@ describe('Config Keys', () => { Strict: true, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from SchemaOptions is not recognized: Strict' @@ -61,7 +65,9 @@ describe('Config Keys', () => { ...defaultConfiguration, unknownKeyName: 'unknownValue', // invalid key }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); // Check if console.warn was called with the expected message expect(console.warn).toHaveBeenCalledWith( @@ -80,7 +86,9 @@ describe('Config Keys', () => { IncludeInternalRequests: false, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from RateLimitOptions is not recognized: IncludeInternalRequests' @@ -95,7 +103,9 @@ describe('Config Keys', () => { EnableCheck: true, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from SecurityOptions is not recognized: EnableCheck' @@ -110,7 +120,9 @@ describe('Config Keys', () => { EnableRouter: true, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from PagesOptions is not recognized: EnableRouter' @@ -129,7 +141,9 @@ describe('Config Keys', () => { ], }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from PagesRoute is not recognized: Handler' @@ -146,7 +160,9 @@ describe('Config Keys', () => { }, }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from PagesCustomUrlsOptions is not recognized: PasswordReset' @@ -161,7 +177,9 @@ describe('Config Keys', () => { InvalidLink: '', // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from CustomPagesOptions is not recognized: InvalidLink' @@ -176,7 +194,9 @@ describe('Config Keys', () => { ClassNames: '', // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from LiveQueryOptions is not recognized: ClassNames' @@ -191,7 +211,9 @@ describe('Config Keys', () => { Ttl: 10, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from IdempotencyOptions is not recognized: Ttl' @@ -208,7 +230,9 @@ describe('Config Keys', () => { UnlockOnPasswordReset: false, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from AccountLockoutOptions is not recognized: UnlockOnPasswordReset' ); @@ -222,7 +246,9 @@ describe('Config Keys', () => { MaxPasswordAge: 10, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from PasswordPolicyOptions is not recognized: MaxPasswordAge' ); @@ -236,7 +262,9 @@ describe('Config Keys', () => { EnableForAnonymousUser: false, // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from FileUploadOptions is not recognized: EnableForAnonymousUser' ); @@ -253,7 +281,9 @@ describe('Config Keys', () => { expect(() => { new ParseServer(dbConfig); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from DatabaseOptions is not recognized: SchemaCacheTtl' ); @@ -267,7 +297,9 @@ describe('Config Keys', () => { CloudFunctionError: 'error', // invalid key }, }); - }).toThrowError('Some Unknown Keys Found in Configuration. See warning messages above.'); + }).toThrowError( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); expect(console.warn).toHaveBeenCalledWith( 'Warning: The following key from LogLevels is not recognized: CloudFunctionError' ); @@ -295,7 +327,7 @@ describe('Config Keys Checked on Server Start', () => { ); expect(err.message).toBe( - 'Some Unknown Keys Found in Configuration. See warning messages above.' + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); } }); diff --git a/src/ParseServer.js b/src/ParseServer.js index 986a2f5a46..00f17c5f01 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -85,7 +85,9 @@ class ParseServer { const allControllers = controllers.getControllers(options); if (Config.failedConfigKeyVerification) { delete Config.failedConfigKeyVerification; - throw new Error('Some Unknown Keys Found in Configuration. See warning messages above.'); + throw new Error( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); } options.state = 'initialized'; @@ -428,7 +430,9 @@ class ParseServer { if (Config.failedConfigKeyVerification) { delete Config.failedConfigKeyVerification; - throw new Error('Unknown key(s) found in Parse Server configuration, see other warning messages for details.'); + throw new Error( + 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' + ); } } } From 7819bdbbf82908a9e7478c2eb0dc0f41b84cbe01 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Mon, 18 Mar 2024 19:42:33 +0000 Subject: [PATCH 11/22] refactor: removing duplication --- src/Config.js | 1 + src/ParseServer.js | 22 +--------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/Config.js b/src/Config.js index 5796cbb6ea..edc9c1b773 100644 --- a/src/Config.js +++ b/src/Config.js @@ -100,6 +100,7 @@ export class Config { 'generateEmailVerifyTokenExpiresAt', 'version', 'RateLimitZone', + 'setIdempotencyFunction', // From Test files 'retryWrites', diff --git a/src/ParseServer.js b/src/ParseServer.js index 00f17c5f01..4a94434528 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,10 +45,7 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; -import { - ParseServerOptions as ParseServerOptionDef, - LiveQueryServerOptions as LiveQueryServerOptionsDef, -} from './Options/Definitions'; +import { ParseServerOptions as ParseServerOptionDef } from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -420,23 +417,6 @@ class ParseServer { config: LiveQueryServerOptions, options: ParseServerOptions ) { - if (config) { - if (Object.prototype.toString.call(config) === '[object Object]') { - Config.validateConfigKeyNames( - Object.keys(LiveQueryServerOptionsDef), - Object.keys(config), - 'LiveQueryServerOptions' - ); - - if (Config.failedConfigKeyVerification) { - delete Config.failedConfigKeyVerification; - throw new Error( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - } - } - } - if (!httpServer || (config && config.port)) { var app = express(); httpServer = require('http').createServer(app); From 05ce8062a1e6a8bfe487d7c9e7a2e5cf79931488 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Mon, 18 Mar 2024 19:42:33 +0000 Subject: [PATCH 12/22] refactor: removing duplication feat: Upgrade to Parse JS SDK 5 (#9022) chore(release): 7.0.0-alpha.28 [skip ci] * Upgrade to Parse JS SDK 5 ([#9022](https://github.com/parse-community/parse-server/issues/9022)) ([ad4aa83](https://github.com/parse-community/parse-server/commit/ad4aa83983205a0e27639f6ee6a4a5963b67e4b8)) fix: Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) (#9024) chore(release): 7.0.0-alpha.29 [skip ci] * Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) ([#9024](https://github.com/parse-community/parse-server/issues/9024)) ([9f6e342](https://github.com/parse-community/parse-server/commit/9f6e3429d3b326cf4e2994733c618d08032fac6e)) empty chore(release): 7.0.0-beta.1 [skip ci] * CacheAdapter does not connect when using a CacheAdapter with a JSON config ([#8633](https://github.com/parse-community/parse-server/issues/8633)) ([720d24e](https://github.com/parse-community/parse-server/commit/720d24e18540da35d50957f17be878316ec30318)) * Conditional email verification not working in some cases if `verifyUserEmails`, `preventLoginWithUnverifiedEmail` set to functions ([#8838](https://github.com/parse-community/parse-server/issues/8838)) ([8e7a6b1](https://github.com/parse-community/parse-server/commit/8e7a6b1480c0117e6c73e7adc5a6619115a04e85)) * Deny request if master key is not set in Parse Server option `masterKeyIps` regardless of ACL and CLP ([#8957](https://github.com/parse-community/parse-server/issues/8957)) ([a7b5b38](https://github.com/parse-community/parse-server/commit/a7b5b38418cbed9be3f4a7665f25b97f592663e1)) * Docker image not published to Docker Hub on new release ([#8905](https://github.com/parse-community/parse-server/issues/8905)) ([a2ac8d1](https://github.com/parse-community/parse-server/commit/a2ac8d133c71cd7b61e5ef59c4be915cfea85db6)) * Docker version releases by removing arm/v6 and arm/v7 support ([#8976](https://github.com/parse-community/parse-server/issues/8976)) ([1f62dd0](https://github.com/parse-community/parse-server/commit/1f62dd0f4e107b22a387692558a042ee26ce8703)) * GraphQL file upload fails in case of use of pointer or relation ([#8721](https://github.com/parse-community/parse-server/issues/8721)) ([1aba638](https://github.com/parse-community/parse-server/commit/1aba6382c873fb489d4a898d301e6da9fb6aa61b)) * Improve PostgreSQL injection detection; fixes security vulnerability [GHSA-6927-3vr9-fxf2](https://github.com/parse-community/parse-server/security/advisories/GHSA-6927-3vr9-fxf2) which affects Parse Server deployments using a Postgres database ([#8961](https://github.com/parse-community/parse-server/issues/8961)) ([cbefe77](https://github.com/parse-community/parse-server/commit/cbefe770a7260b54748a058b8a7389937dc35833)) * Incomplete user object in `verifyEmail` function if both username and email are changed ([#8889](https://github.com/parse-community/parse-server/issues/8889)) ([1eb95ae](https://github.com/parse-community/parse-server/commit/1eb95aeb41a96250e582d79a703f6adcb403c08b)) * Parse Server option `emailVerifyTokenReuseIfValid: true` generates new token on every email verification request ([#8885](https://github.com/parse-community/parse-server/issues/8885)) ([0023ce4](https://github.com/parse-community/parse-server/commit/0023ce448a5e9423337d0e1a25648bde1156bc95)) * Parse Server option `fileExtensions` default value rejects file extensions that are less than 3 or more than 4 characters long ([#8699](https://github.com/parse-community/parse-server/issues/8699)) ([2760381](https://github.com/parse-community/parse-server/commit/276038118377c2b22381bcd8d30337203822121b)) * Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) ([#9024](https://github.com/parse-community/parse-server/issues/9024)) ([9f6e342](https://github.com/parse-community/parse-server/commit/9f6e3429d3b326cf4e2994733c618d08032fac6e)) * Server crashes when receiving an array of `Parse.Pointer` in the request body ([#8784](https://github.com/parse-community/parse-server/issues/8784)) ([66e3603](https://github.com/parse-community/parse-server/commit/66e36039d8af654cfa0284666c0ddd94975dcb52)) * Username is `undefined` in email verification link on email change ([#8887](https://github.com/parse-community/parse-server/issues/8887)) ([e315c13](https://github.com/parse-community/parse-server/commit/e315c137bf41bedfa8f0df537f2c3f6ab45b7e60)) * Add `installationId` to arguments for `verifyUserEmails`, `preventLoginWithUnverifiedEmail` ([#8836](https://github.com/parse-community/parse-server/issues/8836)) ([a22dbe1](https://github.com/parse-community/parse-server/commit/a22dbe16d5ac0090608f6caaf0ebd134925b7fd4)) * Add `installationId`, `ip`, `resendRequest` to arguments passed to `verifyUserEmails` on verification email request ([#8873](https://github.com/parse-community/parse-server/issues/8873)) ([8adcbee](https://github.com/parse-community/parse-server/commit/8adcbee11283d3e95179ca2047e2615f52c18806)) * Add `Parse.User` as function parameter to Parse Server options `verifyUserEmails`, `preventLoginWithUnverifiedEmail` on login ([#8850](https://github.com/parse-community/parse-server/issues/8850)) ([972f630](https://github.com/parse-community/parse-server/commit/972f6300163b3cd7d95eeb95986e8322c95f821c)) * Add password validation via POST request for user with unverified email using master key and option `ignoreEmailVerification` ([#8895](https://github.com/parse-community/parse-server/issues/8895)) ([633a9d2](https://github.com/parse-community/parse-server/commit/633a9d25e4253e2125bc93c02ee8a37e0f5f7b83)) * Add support for MongoDB 7 ([#8761](https://github.com/parse-community/parse-server/issues/8761)) ([3de8494](https://github.com/parse-community/parse-server/commit/3de8494a221991dfd10a74e0a2dc89576265c9b7)) * Add support for MongoDB query comment ([#8928](https://github.com/parse-community/parse-server/issues/8928)) ([2170962](https://github.com/parse-community/parse-server/commit/2170962a50fa353ed85eda3f11dce7ee3647b087)) * Add support for Node 20, drop support for Node 14, 16 ([#8907](https://github.com/parse-community/parse-server/issues/8907)) ([ced4872](https://github.com/parse-community/parse-server/commit/ced487246ea0ef72a8aa014991f003209b34841e)) * Add support for Postgres 16 ([#8898](https://github.com/parse-community/parse-server/issues/8898)) ([99489b2](https://github.com/parse-community/parse-server/commit/99489b22e4f0982e6cb39992974b51aa8d3a31e4)) * Allow `Parse.Session.current` on expired session token instead of throwing error ([#8722](https://github.com/parse-community/parse-server/issues/8722)) ([f9dde4a](https://github.com/parse-community/parse-server/commit/f9dde4a9f8a90c63f71172c9bc515b0f6c6d2e4a)) * Deprecation DEPPS5: Config option `allowClientClassCreation` defaults to `false` ([#8849](https://github.com/parse-community/parse-server/issues/8849)) ([29624e0](https://github.com/parse-community/parse-server/commit/29624e0fae17161cd412ae58d35a195cfa286cad)) * Deprecation DEPPS6: Authentication adapters disabled by default ([#8858](https://github.com/parse-community/parse-server/issues/8858)) ([0cf58eb](https://github.com/parse-community/parse-server/commit/0cf58eb8d60c8e5f485764e154f3214c49eee430)) * Deprecation DEPPS7: Remove deprecated Cloud Code file trigger syntax ([#8855](https://github.com/parse-community/parse-server/issues/8855)) ([4e6a375](https://github.com/parse-community/parse-server/commit/4e6a375b5184ae0f7aa256a921eca4021c609435)) * Deprecation DEPPS8: Parse Server option `allowExpiredAuthDataToken` defaults to `false` ([#8860](https://github.com/parse-community/parse-server/issues/8860)) ([e29845f](https://github.com/parse-community/parse-server/commit/e29845f8dacac09ce3093d75c0d92330c24389e8)) * Deprecation DEPPS9: LiveQuery `fields` option is renamed to `keys` ([#8852](https://github.com/parse-community/parse-server/issues/8852)) ([38983e8](https://github.com/parse-community/parse-server/commit/38983e8e9b5cdbd006f311a2338103624137d013)) * Node process exits with error code 1 on uncaught exception to allow custom uncaught exception handling ([#8894](https://github.com/parse-community/parse-server/issues/8894)) ([70c280c](https://github.com/parse-community/parse-server/commit/70c280ca578ff28b5acf92f37fbe06d42a5b34ca)) * Switch GraphQL server from Yoga v2 to Apollo v4 ([#8959](https://github.com/parse-community/parse-server/issues/8959)) ([105ae7c](https://github.com/parse-community/parse-server/commit/105ae7c8a57d5a650b243174a80c26bf6db16e28)) * Upgrade Parse Server Push Adapter to 5.0.2 ([#8813](https://github.com/parse-community/parse-server/issues/8813)) ([6ef1986](https://github.com/parse-community/parse-server/commit/6ef1986c03a1d84b7e11c05851e5bf9688d88740)) * Upgrade to Parse JS SDK 5 ([#9022](https://github.com/parse-community/parse-server/issues/9022)) ([ad4aa83](https://github.com/parse-community/parse-server/commit/ad4aa83983205a0e27639f6ee6a4a5963b67e4b8)) * Improved IP validation performance for `masterKeyIPs`, `maintenanceKeyIPs` ([#8510](https://github.com/parse-community/parse-server/issues/8510)) ([b87daba](https://github.com/parse-community/parse-server/commit/b87daba0671a1b0b7b8d63bc671d665c91a04522)) * The Parse Server option `allowClientClassCreation` defaults to `false`. ([29624e0](29624e0)) * A request using the master key will now be rejected as unauthorized if the IP from which the request originates is not set in the Parse Server option `masterKeyIps`, even if the request does not require the master key permission, for example for a public object in a public class class. ([a7b5b38](a7b5b38)) * Node process now exits with code 1 on uncaught exceptions, enabling custom handlers that were blocked by Parse Server's default behavior of re-throwing errors. This change may lead to automatic process restarts by the environment, unlike before. ([70c280c](70c280c)) * Authentication adapters are disabled by default; to use an authentication adapter it needs to be explicitly enabled in the Parse Server authentication adapter option `auth..enabled: true` ([0cf58eb](0cf58eb)) * Parse Server option `allowExpiredAuthDataToken` defaults to `false`; a 3rd party authentication token will be validated every time the user tries to log in and the login will fail if the token has expired; the effect of this change may differ for different authentication adapters, depending on the token lifetime and the token refresh logic of the adapter ([e29845f](e29845f)) * LiveQuery `fields` option is renamed to `keys` ([38983e8](38983e8)) * Cloud Code file trigger syntax has been aligned with object trigger syntax, for example `Parse.Cloud.beforeDeleteFile'` has been changed to `Parse.Cloud.beforeDelete(Parse.File, (request) => {})'` ([4e6a375](4e6a375)) * Removes support for Node 14 and 16 ([ced4872](ced4872)) * Removes support for Postgres 11 and 12 ([99489b2](99489b2)) * The `Parse.User` passed as argument if `verifyUserEmails` is set to a function is renamed from `user` to `object` for consistency with invocations of `verifyUserEmails` on signup or login; the user object is not a plain JavaScript object anymore but an instance of `Parse.User` ([8adcbee](8adcbee)) * `Parse.Session.current()` no longer throws an error if the session token is expired, but instead returns the session token with its expiration date to allow checking its validity ([f9dde4a](f9dde4a)) * `Parse.Query` no longer supports the BSON type `code`; although this feature was never officially documented, its removal is announced as a breaking change to protect deployments where it might be in use. ([3de8494](3de8494)) ci: Add lint rule for curly braces (#9032) refactor: Dry `handleAuthData` for safer code maintenance in the future (#9025) refactor: Upgrade @graphql-tools/merge from 8.4.1 to 9.0.3 (#9030) refactor: Bump mongodb-runner from 5.4.4 to 5.5.4 (#9036) fix: `Required` option not handled correctly for special fields (File, GeoPoint, Polygon) on GraphQL API mutations (#8915) chore(release): 7.0.0-alpha.30 [skip ci] * `Required` option not handled correctly for special fields (File, GeoPoint, Polygon) on GraphQL API mutations ([#8915](https://github.com/parse-community/parse-server/issues/8915)) ([907ad42](https://github.com/parse-community/parse-server/commit/907ad4267c228d26cfcefe7848b30ce85ba7ff8f)) refactor: removing duplication --- .eslintrc.json | 3 +- changelogs/CHANGELOG_alpha.md | 21 + changelogs/CHANGELOG_beta.md | 60 +++ package-lock.json | 603 ++++++++++++++++++++------ package.json | 10 +- spec/AuthenticationAdaptersV2.spec.js | 27 ++ spec/ParseGraphQLServer.spec.js | 65 +++ spec/ParseHooks.spec.js | 33 ++ src/Config.js | 1 + src/GraphQL/transformers/mutation.js | 18 +- src/ParseServer.js | 28 +- src/RestWrite.js | 14 +- src/triggers.js | 8 +- 13 files changed, 719 insertions(+), 172 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c04e2d3109..ac68f77de4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,7 +24,8 @@ "prefer-const": "error", "space-infix-ops": "error", "no-useless-escape": "off", - "require-atomic-updates": "off" + "require-atomic-updates": "off", + "object-curly-spacing": ["error", "always"] }, "globals": { "Parse": true diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index ebe3ddae68..0e67dc8c94 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,24 @@ +# [7.0.0-alpha.30](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.29...7.0.0-alpha.30) (2024-03-20) + + +### Bug Fixes + +* `Required` option not handled correctly for special fields (File, GeoPoint, Polygon) on GraphQL API mutations ([#8915](https://github.com/parse-community/parse-server/issues/8915)) ([907ad42](https://github.com/parse-community/parse-server/commit/907ad4267c228d26cfcefe7848b30ce85ba7ff8f)) + +# [7.0.0-alpha.29](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.28...7.0.0-alpha.29) (2024-03-19) + + +### Bug Fixes + +* Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) ([#9024](https://github.com/parse-community/parse-server/issues/9024)) ([9f6e342](https://github.com/parse-community/parse-server/commit/9f6e3429d3b326cf4e2994733c618d08032fac6e)) + +# [7.0.0-alpha.28](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.27...7.0.0-alpha.28) (2024-03-17) + + +### Features + +* Upgrade to Parse JS SDK 5 ([#9022](https://github.com/parse-community/parse-server/issues/9022)) ([ad4aa83](https://github.com/parse-community/parse-server/commit/ad4aa83983205a0e27639f6ee6a4a5963b67e4b8)) + # [7.0.0-alpha.27](https://github.com/parse-community/parse-server/compare/7.0.0-alpha.26...7.0.0-alpha.27) (2024-03-15) diff --git a/changelogs/CHANGELOG_beta.md b/changelogs/CHANGELOG_beta.md index dd80a35e3e..2db9aed2f1 100644 --- a/changelogs/CHANGELOG_beta.md +++ b/changelogs/CHANGELOG_beta.md @@ -1,3 +1,63 @@ +# [7.0.0-beta.1](https://github.com/parse-community/parse-server/compare/6.5.0-beta.1...7.0.0-beta.1) (2024-03-19) + + +### Bug Fixes + +* CacheAdapter does not connect when using a CacheAdapter with a JSON config ([#8633](https://github.com/parse-community/parse-server/issues/8633)) ([720d24e](https://github.com/parse-community/parse-server/commit/720d24e18540da35d50957f17be878316ec30318)) +* Conditional email verification not working in some cases if `verifyUserEmails`, `preventLoginWithUnverifiedEmail` set to functions ([#8838](https://github.com/parse-community/parse-server/issues/8838)) ([8e7a6b1](https://github.com/parse-community/parse-server/commit/8e7a6b1480c0117e6c73e7adc5a6619115a04e85)) +* Deny request if master key is not set in Parse Server option `masterKeyIps` regardless of ACL and CLP ([#8957](https://github.com/parse-community/parse-server/issues/8957)) ([a7b5b38](https://github.com/parse-community/parse-server/commit/a7b5b38418cbed9be3f4a7665f25b97f592663e1)) +* Docker image not published to Docker Hub on new release ([#8905](https://github.com/parse-community/parse-server/issues/8905)) ([a2ac8d1](https://github.com/parse-community/parse-server/commit/a2ac8d133c71cd7b61e5ef59c4be915cfea85db6)) +* Docker version releases by removing arm/v6 and arm/v7 support ([#8976](https://github.com/parse-community/parse-server/issues/8976)) ([1f62dd0](https://github.com/parse-community/parse-server/commit/1f62dd0f4e107b22a387692558a042ee26ce8703)) +* GraphQL file upload fails in case of use of pointer or relation ([#8721](https://github.com/parse-community/parse-server/issues/8721)) ([1aba638](https://github.com/parse-community/parse-server/commit/1aba6382c873fb489d4a898d301e6da9fb6aa61b)) +* Improve PostgreSQL injection detection; fixes security vulnerability [GHSA-6927-3vr9-fxf2](https://github.com/parse-community/parse-server/security/advisories/GHSA-6927-3vr9-fxf2) which affects Parse Server deployments using a Postgres database ([#8961](https://github.com/parse-community/parse-server/issues/8961)) ([cbefe77](https://github.com/parse-community/parse-server/commit/cbefe770a7260b54748a058b8a7389937dc35833)) +* Incomplete user object in `verifyEmail` function if both username and email are changed ([#8889](https://github.com/parse-community/parse-server/issues/8889)) ([1eb95ae](https://github.com/parse-community/parse-server/commit/1eb95aeb41a96250e582d79a703f6adcb403c08b)) +* Parse Server option `emailVerifyTokenReuseIfValid: true` generates new token on every email verification request ([#8885](https://github.com/parse-community/parse-server/issues/8885)) ([0023ce4](https://github.com/parse-community/parse-server/commit/0023ce448a5e9423337d0e1a25648bde1156bc95)) +* Parse Server option `fileExtensions` default value rejects file extensions that are less than 3 or more than 4 characters long ([#8699](https://github.com/parse-community/parse-server/issues/8699)) ([2760381](https://github.com/parse-community/parse-server/commit/276038118377c2b22381bcd8d30337203822121b)) +* Server crashes on invalid Cloud Function or Cloud Job name; fixes security vulnerability [GHSA-6hh7-46r2-vf29](https://github.com/parse-community/parse-server/security/advisories/GHSA-6hh7-46r2-vf29) ([#9024](https://github.com/parse-community/parse-server/issues/9024)) ([9f6e342](https://github.com/parse-community/parse-server/commit/9f6e3429d3b326cf4e2994733c618d08032fac6e)) +* Server crashes when receiving an array of `Parse.Pointer` in the request body ([#8784](https://github.com/parse-community/parse-server/issues/8784)) ([66e3603](https://github.com/parse-community/parse-server/commit/66e36039d8af654cfa0284666c0ddd94975dcb52)) +* Username is `undefined` in email verification link on email change ([#8887](https://github.com/parse-community/parse-server/issues/8887)) ([e315c13](https://github.com/parse-community/parse-server/commit/e315c137bf41bedfa8f0df537f2c3f6ab45b7e60)) + +### Features + +* Add `installationId` to arguments for `verifyUserEmails`, `preventLoginWithUnverifiedEmail` ([#8836](https://github.com/parse-community/parse-server/issues/8836)) ([a22dbe1](https://github.com/parse-community/parse-server/commit/a22dbe16d5ac0090608f6caaf0ebd134925b7fd4)) +* Add `installationId`, `ip`, `resendRequest` to arguments passed to `verifyUserEmails` on verification email request ([#8873](https://github.com/parse-community/parse-server/issues/8873)) ([8adcbee](https://github.com/parse-community/parse-server/commit/8adcbee11283d3e95179ca2047e2615f52c18806)) +* Add `Parse.User` as function parameter to Parse Server options `verifyUserEmails`, `preventLoginWithUnverifiedEmail` on login ([#8850](https://github.com/parse-community/parse-server/issues/8850)) ([972f630](https://github.com/parse-community/parse-server/commit/972f6300163b3cd7d95eeb95986e8322c95f821c)) +* Add password validation via POST request for user with unverified email using master key and option `ignoreEmailVerification` ([#8895](https://github.com/parse-community/parse-server/issues/8895)) ([633a9d2](https://github.com/parse-community/parse-server/commit/633a9d25e4253e2125bc93c02ee8a37e0f5f7b83)) +* Add support for MongoDB 7 ([#8761](https://github.com/parse-community/parse-server/issues/8761)) ([3de8494](https://github.com/parse-community/parse-server/commit/3de8494a221991dfd10a74e0a2dc89576265c9b7)) +* Add support for MongoDB query comment ([#8928](https://github.com/parse-community/parse-server/issues/8928)) ([2170962](https://github.com/parse-community/parse-server/commit/2170962a50fa353ed85eda3f11dce7ee3647b087)) +* Add support for Node 20, drop support for Node 14, 16 ([#8907](https://github.com/parse-community/parse-server/issues/8907)) ([ced4872](https://github.com/parse-community/parse-server/commit/ced487246ea0ef72a8aa014991f003209b34841e)) +* Add support for Postgres 16 ([#8898](https://github.com/parse-community/parse-server/issues/8898)) ([99489b2](https://github.com/parse-community/parse-server/commit/99489b22e4f0982e6cb39992974b51aa8d3a31e4)) +* Allow `Parse.Session.current` on expired session token instead of throwing error ([#8722](https://github.com/parse-community/parse-server/issues/8722)) ([f9dde4a](https://github.com/parse-community/parse-server/commit/f9dde4a9f8a90c63f71172c9bc515b0f6c6d2e4a)) +* Deprecation DEPPS5: Config option `allowClientClassCreation` defaults to `false` ([#8849](https://github.com/parse-community/parse-server/issues/8849)) ([29624e0](https://github.com/parse-community/parse-server/commit/29624e0fae17161cd412ae58d35a195cfa286cad)) +* Deprecation DEPPS6: Authentication adapters disabled by default ([#8858](https://github.com/parse-community/parse-server/issues/8858)) ([0cf58eb](https://github.com/parse-community/parse-server/commit/0cf58eb8d60c8e5f485764e154f3214c49eee430)) +* Deprecation DEPPS7: Remove deprecated Cloud Code file trigger syntax ([#8855](https://github.com/parse-community/parse-server/issues/8855)) ([4e6a375](https://github.com/parse-community/parse-server/commit/4e6a375b5184ae0f7aa256a921eca4021c609435)) +* Deprecation DEPPS8: Parse Server option `allowExpiredAuthDataToken` defaults to `false` ([#8860](https://github.com/parse-community/parse-server/issues/8860)) ([e29845f](https://github.com/parse-community/parse-server/commit/e29845f8dacac09ce3093d75c0d92330c24389e8)) +* Deprecation DEPPS9: LiveQuery `fields` option is renamed to `keys` ([#8852](https://github.com/parse-community/parse-server/issues/8852)) ([38983e8](https://github.com/parse-community/parse-server/commit/38983e8e9b5cdbd006f311a2338103624137d013)) +* Node process exits with error code 1 on uncaught exception to allow custom uncaught exception handling ([#8894](https://github.com/parse-community/parse-server/issues/8894)) ([70c280c](https://github.com/parse-community/parse-server/commit/70c280ca578ff28b5acf92f37fbe06d42a5b34ca)) +* Switch GraphQL server from Yoga v2 to Apollo v4 ([#8959](https://github.com/parse-community/parse-server/issues/8959)) ([105ae7c](https://github.com/parse-community/parse-server/commit/105ae7c8a57d5a650b243174a80c26bf6db16e28)) +* Upgrade Parse Server Push Adapter to 5.0.2 ([#8813](https://github.com/parse-community/parse-server/issues/8813)) ([6ef1986](https://github.com/parse-community/parse-server/commit/6ef1986c03a1d84b7e11c05851e5bf9688d88740)) +* Upgrade to Parse JS SDK 5 ([#9022](https://github.com/parse-community/parse-server/issues/9022)) ([ad4aa83](https://github.com/parse-community/parse-server/commit/ad4aa83983205a0e27639f6ee6a4a5963b67e4b8)) + +### Performance Improvements + +* Improved IP validation performance for `masterKeyIPs`, `maintenanceKeyIPs` ([#8510](https://github.com/parse-community/parse-server/issues/8510)) ([b87daba](https://github.com/parse-community/parse-server/commit/b87daba0671a1b0b7b8d63bc671d665c91a04522)) + + +### BREAKING CHANGES + +* The Parse Server option `allowClientClassCreation` defaults to `false`. ([29624e0](29624e0)) +* A request using the master key will now be rejected as unauthorized if the IP from which the request originates is not set in the Parse Server option `masterKeyIps`, even if the request does not require the master key permission, for example for a public object in a public class class. ([a7b5b38](a7b5b38)) +* Node process now exits with code 1 on uncaught exceptions, enabling custom handlers that were blocked by Parse Server's default behavior of re-throwing errors. This change may lead to automatic process restarts by the environment, unlike before. ([70c280c](70c280c)) +* Authentication adapters are disabled by default; to use an authentication adapter it needs to be explicitly enabled in the Parse Server authentication adapter option `auth..enabled: true` ([0cf58eb](0cf58eb)) +* Parse Server option `allowExpiredAuthDataToken` defaults to `false`; a 3rd party authentication token will be validated every time the user tries to log in and the login will fail if the token has expired; the effect of this change may differ for different authentication adapters, depending on the token lifetime and the token refresh logic of the adapter ([e29845f](e29845f)) +* LiveQuery `fields` option is renamed to `keys` ([38983e8](38983e8)) +* Cloud Code file trigger syntax has been aligned with object trigger syntax, for example `Parse.Cloud.beforeDeleteFile'` has been changed to `Parse.Cloud.beforeDelete(Parse.File, (request) => {})'` ([4e6a375](4e6a375)) +* Removes support for Node 14 and 16 ([ced4872](ced4872)) +* Removes support for Postgres 11 and 12 ([99489b2](99489b2)) +* The `Parse.User` passed as argument if `verifyUserEmails` is set to a function is renamed from `user` to `object` for consistency with invocations of `verifyUserEmails` on signup or login; the user object is not a plain JavaScript object anymore but an instance of `Parse.User` ([8adcbee](8adcbee)) +* `Parse.Session.current()` no longer throws an error if the session token is expired, but instead returns the session token with its expiration date to allow checking its validity ([f9dde4a](f9dde4a)) +* `Parse.Query` no longer supports the BSON type `code`; although this feature was never officially documented, its removal is announced as a breaking change to protect deployments where it might be in use. ([3de8494](3de8494)) + # [6.5.0-beta.1](https://github.com/parse-community/parse-server/compare/6.4.0...6.5.0-beta.1) (2023-11-16) diff --git a/package-lock.json b/package-lock.json index 2c72c3fee3..786c96bab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "parse-server", - "version": "7.0.0-alpha.27", + "version": "7.0.0-alpha.30", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "7.0.0-alpha.27", + "version": "7.0.0-alpha.30", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@apollo/server": "4.10.1", "@babel/eslint-parser": "7.21.8", - "@graphql-tools/merge": "8.4.1", + "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", "@parse/fs-files-adapter": "2.0.1", @@ -24,7 +24,7 @@ "deepcopy": "2.1.0", "express": "4.18.2", "express-rate-limit": "6.11.2", - "follow-redirects": "^1.15.6", + "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", "graphql-relay": "0.10.0", @@ -40,7 +40,7 @@ "mongodb": "5.9.0", "mustache": "4.2.0", "otpauth": "9.2.2", - "parse": "4.1.0", + "parse": "5.0.0", "path-to-regexp": "6.2.1", "pg-monitor": "2.0.0", "pg-promise": "11.5.4", @@ -92,7 +92,7 @@ "madge": "6.1.0", "mock-files-adapter": "file:spec/dependencies/mock-files-adapter", "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", - "mongodb-runner": "5.4.4", + "mongodb-runner": "5.5.4", "mongodb-version-list": "1.0.0", "node-abort-controller": "3.0.1", "node-fetch": "3.2.10", @@ -279,6 +279,18 @@ "graphql": "14.x || 15.x || 16.x" } }, + "node_modules/@apollo/server/node_modules/@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "dependencies": { + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@apollo/server/node_modules/@graphql-tools/schema": { "version": "9.0.19", "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", @@ -2638,25 +2650,33 @@ } }, "node_modules/@graphql-tools/merge": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.1.tgz", - "integrity": "sha512-hssnPpZ818mxgl5+GfyOOSnnflAxiaTn1A1AojZcIbh4J52sS1Q0gSuBR5VrnUDjuxiqoCotpXdAQl+K+U6KLQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", + "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", "dependencies": { - "@graphql-tools/utils": "^9.2.1", + "@graphql-tools/utils": "^10.0.13", "tslib": "^2.4.0" }, + "engines": { + "node": ">=16.0.0" + }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.2.tgz", + "integrity": "sha512-fX13CYsDnX4yifIyNdiN0cVygz/muvkreWWem6BBw130+ODbRRgfiVveL0NizCEnKXkpvdeTy9Bxvo9LIKlhrw==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", "tslib": "^2.4.0" }, + "engines": { + "node": ">=16.0.0" + }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } @@ -2678,21 +2698,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/merge": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", - "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", - "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.0.tgz", @@ -3141,9 +3146,9 @@ "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" }, "node_modules/@mongodb-js/mongodb-downloader": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.2.8.tgz", - "integrity": "sha512-y+mgw9QspvgTLRNHZJRS+DUTPk45RWpvYD1MaGDWhZ4ajffvxGqanY+Z4R6z01n+tIRmQvpShzF6zk+2Pr9d6w==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.2.10.tgz", + "integrity": "sha512-nM0vUGUcC8DsR6x3AU6032I8b5OaTqAdq9JmanVecq2NyLZu1aacIS9Yqo3QiE2dIlrkn9hcaUg9Z/tL+3QbNA==", "dev": true, "dependencies": { "debug": "^4.3.4", @@ -3196,10 +3201,10 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", - "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", - "optional": true, + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "devOptional": true, "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -5979,9 +5984,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", - "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -11357,7 +11362,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "devOptional": true }, "node_modules/meow": { "version": "8.1.2", @@ -11780,21 +11785,31 @@ } }, "node_modules/mongodb-runner": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.4.4.tgz", - "integrity": "sha512-bODXH7sOMRXbpVceIR8zLnEgg9kv1eXW60pxW5dfNShkjWd+7wmalJY/Dm9LG1tcL7+O5H7lTZH+s32IWg+FVA==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.5.4.tgz", + "integrity": "sha512-BCENSrCixOoEOISh2bBUw+rUcnbAhntJdalvap9CRtsoqzEPzRz+KAIR+mh7PnQvU1fSfbRgB2e6+6x9OGCuPA==", "dev": true, "dependencies": { - "@mongodb-js/mongodb-downloader": "^0.2.8", + "@mongodb-js/mongodb-downloader": "^0.2.10", + "@mongodb-js/saslprep": "^1.1.5", "debug": "^4.3.4", - "mongodb": "^5.6.0", - "mongodb-connection-string-url": "^2.6.0", + "mongodb": "^6.3.0", + "mongodb-connection-string-url": "^3.0.0", "yargs": "^17.7.2" }, "bin": { "mongodb-runner": "bin/runner.js" } }, + "node_modules/mongodb-runner/node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dev": true, + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/mongodb-runner/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11810,6 +11825,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/mongodb-runner/node_modules/bson": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", + "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", + "dev": true, + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/mongodb-runner/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -11842,6 +11866,178 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/mongodb-runner/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-runner/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-runner/node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dev": true, + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-runner/node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dev": true, + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongodb-runner/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/mongodb-runner/node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/mongodb-runner/node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/mongodb-runner/node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/mongodb-runner/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mongodb-runner/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mongodb-runner/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dev": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mongodb-runner/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -15653,22 +15849,22 @@ } }, "node_modules/parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz", - "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-5.0.0.tgz", + "integrity": "sha512-6gOARZWiHjmGusbTskhC1qlRn527olMEsdt2LLj9cP2GY3n4VFOwFwV8z/vm2+YfzPfPcv8z7qLih1NzmqzO0g==", "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "idb-keyval": "6.2.0", + "@babel/runtime-corejs3": "7.23.2", + "idb-keyval": "6.2.1", "react-native-crypto-js": "1.0.0", - "uuid": "9.0.0", - "ws": "8.12.0", + "uuid": "9.0.1", + "ws": "8.16.0", "xmlhttprequest": "1.8.0" }, "engines": { - "node": ">=14.21.0 <17 || >=18 <20" + "node": ">=18 <21" }, "optionalDependencies": { - "crypto-js": "4.1.1" + "crypto-js": "4.2.0" } }, "node_modules/parse-json": { @@ -15698,34 +15894,34 @@ "node": ">=6" } }, - "node_modules/parse/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/parse/node_modules/ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "node_modules/parse/node_modules/@babel/runtime-corejs3": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.2.tgz", + "integrity": "sha512-54cIh74Z1rp4oIjsHjqN+WM4fMyCBYe+LpZ9jWm51CZ1fbH3SkAzQD/3XLoNkjbJ7YEmjobLXyvQrFypRHOrXw==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": ">=6.9.0" } }, + "node_modules/parse/node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "optional": true + }, + "node_modules/parse/node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, + "node_modules/parse/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -17967,7 +18163,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, + "devOptional": true, "dependencies": { "memory-pager": "^1.0.2" } @@ -19841,6 +20037,15 @@ "whatwg-mimetype": "^3.0.0" }, "dependencies": { + "@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "requires": { + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + } + }, "@graphql-tools/schema": { "version": "9.0.19", "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", @@ -21531,20 +21736,22 @@ } }, "@graphql-tools/merge": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.1.tgz", - "integrity": "sha512-hssnPpZ818mxgl5+GfyOOSnnflAxiaTn1A1AojZcIbh4J52sS1Q0gSuBR5VrnUDjuxiqoCotpXdAQl+K+U6KLQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", + "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", "requires": { - "@graphql-tools/utils": "^9.2.1", + "@graphql-tools/utils": "^10.0.13", "tslib": "^2.4.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.2.tgz", + "integrity": "sha512-fX13CYsDnX4yifIyNdiN0cVygz/muvkreWWem6BBw130+ODbRRgfiVveL0NizCEnKXkpvdeTy9Bxvo9LIKlhrw==", "requires": { "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", "tslib": "^2.4.0" } } @@ -21561,15 +21768,6 @@ "value-or-promise": "^1.0.12" }, "dependencies": { - "@graphql-tools/merge": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", - "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", - "requires": { - "@graphql-tools/utils": "^10.0.13", - "tslib": "^2.4.0" - } - }, "@graphql-tools/utils": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.0.tgz", @@ -21930,9 +22128,9 @@ "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" }, "@mongodb-js/mongodb-downloader": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.2.8.tgz", - "integrity": "sha512-y+mgw9QspvgTLRNHZJRS+DUTPk45RWpvYD1MaGDWhZ4ajffvxGqanY+Z4R6z01n+tIRmQvpShzF6zk+2Pr9d6w==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.2.10.tgz", + "integrity": "sha512-nM0vUGUcC8DsR6x3AU6032I8b5OaTqAdq9JmanVecq2NyLZu1aacIS9Yqo3QiE2dIlrkn9hcaUg9Z/tL+3QbNA==", "dev": true, "requires": { "debug": "^4.3.4", @@ -21976,10 +22174,10 @@ } }, "@mongodb-js/saslprep": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", - "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", - "optional": true, + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "devOptional": true, "requires": { "sparse-bitfield": "^3.0.3" } @@ -24111,9 +24309,9 @@ } }, "core-js-pure": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", - "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==" + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==" }, "core-util-is": { "version": "1.0.3", @@ -28238,7 +28436,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "devOptional": true }, "meow": { "version": "8.1.2", @@ -28535,18 +28733,28 @@ } }, "mongodb-runner": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.4.4.tgz", - "integrity": "sha512-bODXH7sOMRXbpVceIR8zLnEgg9kv1eXW60pxW5dfNShkjWd+7wmalJY/Dm9LG1tcL7+O5H7lTZH+s32IWg+FVA==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.5.4.tgz", + "integrity": "sha512-BCENSrCixOoEOISh2bBUw+rUcnbAhntJdalvap9CRtsoqzEPzRz+KAIR+mh7PnQvU1fSfbRgB2e6+6x9OGCuPA==", "dev": true, "requires": { - "@mongodb-js/mongodb-downloader": "^0.2.8", + "@mongodb-js/mongodb-downloader": "^0.2.10", + "@mongodb-js/saslprep": "^1.1.5", "debug": "^4.3.4", - "mongodb": "^5.6.0", - "mongodb-connection-string-url": "^2.6.0", + "mongodb": "^6.3.0", + "mongodb-connection-string-url": "^3.0.0", "yargs": "^17.7.2" }, "dependencies": { + "@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dev": true, + "requires": { + "@types/webidl-conversions": "*" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -28556,6 +28764,12 @@ "color-convert": "^2.0.1" } }, + "bson": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", + "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", + "dev": true + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -28582,6 +28796,119 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + } + }, + "gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, + "mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dev": true, + "requires": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + } + }, + "mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dev": true, + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "optional": true, + "peer": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "optional": true, + "peer": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "requires": { + "punycode": "^2.3.0" + } + }, + "whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dev": true, + "requires": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -31348,29 +31675,43 @@ } }, "parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-4.1.0.tgz", - "integrity": "sha512-s0Ti+nWrKWj9DlFcmkEE05fGwa/K5ycZSdqCz01F8YL7Hevqv4WLXAmYGOwzq5UJSZ005seKgb20KwVwLdy/Zg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-5.0.0.tgz", + "integrity": "sha512-6gOARZWiHjmGusbTskhC1qlRn527olMEsdt2LLj9cP2GY3n4VFOwFwV8z/vm2+YfzPfPcv8z7qLih1NzmqzO0g==", "requires": { - "@babel/runtime-corejs3": "7.21.0", - "crypto-js": "4.1.1", - "idb-keyval": "6.2.0", + "@babel/runtime-corejs3": "7.23.2", + "crypto-js": "4.2.0", + "idb-keyval": "6.2.1", "react-native-crypto-js": "1.0.0", - "uuid": "9.0.0", - "ws": "8.12.0", + "uuid": "9.0.1", + "ws": "8.16.0", "xmlhttprequest": "1.8.0" }, "dependencies": { - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "@babel/runtime-corejs3": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.2.tgz", + "integrity": "sha512-54cIh74Z1rp4oIjsHjqN+WM4fMyCBYe+LpZ9jWm51CZ1fbH3SkAzQD/3XLoNkjbJ7YEmjobLXyvQrFypRHOrXw==", + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } }, - "ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "requires": {} + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "optional": true + }, + "idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" } } }, @@ -33079,7 +33420,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, + "devOptional": true, "requires": { "memory-pager": "^1.0.2" } diff --git a/package.json b/package.json index 0e0414afd6..80cdc1a6a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "7.0.0-alpha.27", + "version": "7.0.0-alpha.30", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -21,7 +21,7 @@ "dependencies": { "@apollo/server": "4.10.1", "@babel/eslint-parser": "7.21.8", - "@graphql-tools/merge": "8.4.1", + "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", "@parse/fs-files-adapter": "2.0.1", @@ -49,7 +49,7 @@ "mongodb": "5.9.0", "mustache": "4.2.0", "otpauth": "9.2.2", - "parse": "4.1.0", + "parse": "5.0.0", "path-to-regexp": "6.2.1", "pg-monitor": "2.0.0", "pg-promise": "11.5.4", @@ -98,7 +98,7 @@ "madge": "6.1.0", "mock-files-adapter": "file:spec/dependencies/mock-files-adapter", "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", - "mongodb-runner": "5.4.4", + "mongodb-runner": "5.5.4", "mongodb-version-list": "1.0.0", "node-abort-controller": "3.0.1", "node-fetch": "3.2.10", @@ -166,4 +166,4 @@ "git add" ] } -} +} \ No newline at end of file diff --git a/spec/AuthenticationAdaptersV2.spec.js b/spec/AuthenticationAdaptersV2.spec.js index aaa172ea66..e9486187ef 100644 --- a/spec/AuthenticationAdaptersV2.spec.js +++ b/spec/AuthenticationAdaptersV2.spec.js @@ -487,6 +487,33 @@ describe('Auth Adapter features', () => { expect(baseAdapter2.validateAuthData).toHaveBeenCalledTimes(2); }); + it('should not perform authData validation twice when data mutated', async () => { + spyOn(baseAdapter, 'validateAuthData').and.resolveTo({}); + await reconfigureServer({ + auth: { baseAdapter }, + allowExpiredAuthDataToken: false, + }); + + const user = new Parse.User(); + + await user.save({ + authData: { + baseAdapter: { id: 'baseAdapter', token: "sometoken1" }, + }, + }); + + expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(1); + + const user2 = new Parse.User(); + await user2.save({ + authData: { + baseAdapter: { id: 'baseAdapter', token: "sometoken2" }, + }, + }); + + expect(baseAdapter.validateAuthData).toHaveBeenCalledTimes(2); + }); + it('should require additional provider if configured', async () => { await reconfigureServer({ auth: { baseAdapter, additionalAdapter }, diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 45d21a2b9b..dafe1a3ee2 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -9548,6 +9548,71 @@ describe('ParseGraphQLServer', () => { } }); + it('should support files on required file', async () => { + try { + parseServer = await global.reconfigureServer({ + publicServerURL: 'http://localhost:13377/parse', + }); + const schemaController = await parseServer.config.databaseController.loadSchema(); + await schemaController.addClassIfNotExists('SomeClassWithRequiredFile', { + someField: { type: 'File', required: true }, + }); + await resetGraphQLCache(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const body = new FormData(); + body.append( + 'operations', + JSON.stringify({ + query: ` + mutation CreateSomeObject( + $fields: CreateSomeClassWithRequiredFileFieldsInput + ) { + createSomeClassWithRequiredFile( + input: { fields: $fields } + ) { + someClassWithRequiredFile { + id + someField { + name + url + } + } + } + } + `, + variables: { + fields: { + someField: { upload: null }, + }, + }, + }) + ); + body.append('map', JSON.stringify({ 1: ['variables.fields.someField.upload'] })); + body.append('1', 'My File Content', { + filename: 'myFileName.txt', + contentType: 'text/plain', + }); + + const res = await fetch('http://localhost:13377/graphql', { + method: 'POST', + headers, + body, + }); + expect(res.status).toEqual(200); + const resText = await res.text(); + const result = JSON.parse(resText); + expect( + result.data.createSomeClassWithRequiredFile.someClassWithRequiredFile.someField.name + ).toEqual(jasmine.stringMatching(/_myFileName.txt$/)); + expect( + result.data.createSomeClassWithRequiredFile.someClassWithRequiredFile.someField.url + ).toEqual(jasmine.stringMatching(/_myFileName.txt$/)); + } catch (e) { + handleError(e); + } + }); + it('should support file upload for on fly creation through pointer and relation', async () => { parseServer = await global.reconfigureServer({ publicServerURL: 'http://localhost:13377/parse', diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 16a2e17be3..8d7c653fa2 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -694,3 +694,36 @@ describe('triggers', () => { expect(req.context).toBeUndefined(); }); }); + +describe('sanitizing names', () => { + const invalidNames = [ + `test'%3bdeclare%20@q%20varchar(99)%3bset%20@q%3d'%5c%5cxxxxxxxxxxxxxxx.yyyyy'%2b'fy.com%5cxus'%3b%20exec%20master.dbo.xp_dirtree%20@q%3b--%20`, + `test.function.name`, + ]; + + it('should not crash server and return error on invalid Cloud Function name', async () => { + for (const invalidName of invalidNames) { + let error; + try { + await Parse.Cloud.run(invalidName); + } catch (err) { + error = err; + } + expect(error).toBeDefined(); + expect(error.message).toMatch(/Invalid function/); + } + }); + + it('should not crash server and return error on invalid Cloud Job name', async () => { + for (const invalidName of invalidNames) { + let error; + try { + await Parse.Cloud.startJob(invalidName); + } catch (err) { + error = err; + } + expect(error).toBeDefined(); + expect(error.message).toMatch(/Invalid job/); + } + }); +}); diff --git a/src/Config.js b/src/Config.js index 5796cbb6ea..edc9c1b773 100644 --- a/src/Config.js +++ b/src/Config.js @@ -100,6 +100,7 @@ export class Config { 'generateEmailVerifyTokenExpiresAt', 'version', 'RateLimitZone', + 'setIdempotencyFunction', // From Test files 'retryWrites', diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index 17dd6a8d4b..1f1fd16ead 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -1,7 +1,6 @@ import Parse from 'parse/node'; import { fromGlobalId } from 'graphql-relay'; import { handleUpload } from '../loaders/filesMutations'; -import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes'; import * as objectsMutations from '../helpers/objectsMutations'; const transformTypes = async ( @@ -28,27 +27,28 @@ const transformTypes = async ( inputTypeField = classGraphQLUpdateTypeFields[field]; } if (inputTypeField) { - switch (true) { - case inputTypeField.type === defaultGraphQLTypes.GEO_POINT_INPUT: + const parseFieldType = parseClass.fields[field].type; + switch (parseFieldType) { + case 'GeoPoint': if (fields[field] === null) { fields[field] = { __op: 'Delete' }; break; } fields[field] = transformers.geoPoint(fields[field]); break; - case inputTypeField.type === defaultGraphQLTypes.POLYGON_INPUT: + case 'Polygon': if (fields[field] === null) { fields[field] = { __op: 'Delete' }; break; } fields[field] = transformers.polygon(fields[field]); break; - case inputTypeField.type === defaultGraphQLTypes.FILE_INPUT: - // Use `originalFields` to handle file upload since fields are a deepcopy and do not - // keep the file object + case 'File': + // We need to use the originalFields to handle the file upload + // since fields are a deepcopy and do not keep the file object fields[field] = await transformers.file(originalFields[field], req); break; - case parseClass.fields[field].type === 'Relation': + case 'Relation': fields[field] = await transformers.relation( parseClass.fields[field].targetClass, field, @@ -58,7 +58,7 @@ const transformTypes = async ( req ); break; - case parseClass.fields[field].type === 'Pointer': + case 'Pointer': if (fields[field] === null) { fields[field] = { __op: 'Delete' }; break; diff --git a/src/ParseServer.js b/src/ParseServer.js index 00f17c5f01..ce2d60b6e3 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -70,6 +70,17 @@ class ParseServer { 'ParseServerOptions' ); + if ( + options.liveQueryServerOptions && + Object.prototype.toString.call(options.liveQueryServerOptions) === '[object Object]' + ) { + Config.validateConfigKeyNames( + Object.keys(LiveQueryServerOptionsDef), + Object.keys(options.liveQueryServerOptions), + 'LiveQueryServerOptions' + ); + } + // Set option defaults injectDefaults(options); const { @@ -420,23 +431,6 @@ class ParseServer { config: LiveQueryServerOptions, options: ParseServerOptions ) { - if (config) { - if (Object.prototype.toString.call(config) === '[object Object]') { - Config.validateConfigKeyNames( - Object.keys(LiveQueryServerOptionsDef), - Object.keys(config), - 'LiveQueryServerOptions' - ); - - if (Config.failedConfigKeyVerification) { - delete Config.failedConfigKeyVerification; - throw new Error( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - } - } - } - if (!httpServer || (config && config.port)) { var app = express(); httpServer = require('http').createServer(app); diff --git a/src/RestWrite.js b/src/RestWrite.js index 7243238cfa..c2d4e1580c 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -523,10 +523,14 @@ RestWrite.prototype.handleAuthData = async function (authData) { const r = await Auth.findUsersWithAuthData(this.config, authData); const results = this.filteredObjectsByACL(r); - if (results.length > 1) { + const userId = this.getUserId(); + const userResult = results[0]; + const foundUserIsNotCurrentUser = userId && userResult && userId !== userResult.objectId; + + if (results.length > 1 || foundUserIsNotCurrentUser) { // To avoid https://github.com/parse-community/parse-server/security/advisories/GHSA-8w3j-g983-8jh5 // Let's run some validation before throwing - await Auth.handleAuthDataValidation(authData, this, results[0]); + await Auth.handleAuthDataValidation(authData, this, userResult); throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); } @@ -544,12 +548,6 @@ RestWrite.prototype.handleAuthData = async function (authData) { // User found with provided authData if (results.length === 1) { - const userId = this.getUserId(); - const userResult = results[0]; - // Prevent duplicate authData id - if (userId && userId !== userResult.objectId) { - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); - } this.storage.authProvider = Object.keys(authData).join(','); diff --git a/src/triggers.js b/src/triggers.js index bfebde2e83..34ace35b4b 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -86,6 +86,12 @@ const Category = { }; function getStore(category, name, applicationId) { + const invalidNameRegex = /['"`]/; + if (invalidNameRegex.test(name)) { + // Prevent a malicious user from injecting properties into the store + return {}; + } + const path = name.split('.'); path.splice(-1); // remove last component applicationId = applicationId || Parse.applicationId; @@ -94,7 +100,7 @@ function getStore(category, name, applicationId) { for (const component of path) { store = store[component]; if (!store) { - return undefined; + return {}; } } return store; From c5c2b5d6225d35c2d9aeab5e95bb4dfbe9b31c60 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:47:58 +0000 Subject: [PATCH 13/22] fix: import error --- src/ParseServer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ParseServer.js b/src/ParseServer.js index e5cb9f613b..ce2d60b6e3 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,7 +45,10 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; -import { ParseServerOptions as ParseServerOptionDef } from './Options/Definitions'; +import { + ParseServerOptions as ParseServerOptionDef, + LiveQueryServerOptions as LiveQueryServerOptionsDef, +} from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); From 53f1d93ec15b6b1bf033726d45fd3406604e9b8e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:19:07 +0100 Subject: [PATCH 14/22] nits Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- package.json | 2 +- src/Config.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 80cdc1a6a6..ddc3d47cb9 100644 --- a/package.json +++ b/package.json @@ -166,4 +166,4 @@ "git add" ] } -} \ No newline at end of file +} diff --git a/src/Config.js b/src/Config.js index edc9c1b773..78386fcdb1 100644 --- a/src/Config.js +++ b/src/Config.js @@ -102,7 +102,7 @@ export class Config { 'RateLimitZone', 'setIdempotencyFunction', - // From Test files + // From test files 'retryWrites', 'customIdSize', 'path', @@ -112,7 +112,9 @@ export class Config { ]; for (const key of given) { - if (internalConfigVars.includes(key)) continue; + if (internalConfigVars.includes(key)) { + continue; + } if (!actual.includes(key)) { Config.failedConfigKeyVerification = true; From e333d7369b6bedb51de7996a67f017132047404b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:39:40 +0100 Subject: [PATCH 15/22] suggestion --- src/ParseServer.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/ParseServer.js b/src/ParseServer.js index ce2d60b6e3..a6362e52ae 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -49,6 +49,7 @@ import { ParseServerOptions as ParseServerOptionDef, LiveQueryServerOptions as LiveQueryServerOptionsDef, } from './Options/Definitions'; +import OptionsDefinitions from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -64,6 +65,64 @@ class ParseServer { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); + // START + // // Clone interfaces + // let interfaces = JSON.parse(JSON.stringify(OptionsDefinitions)); + + // // Remove internal interface keys + // function recursivelyRemoveKeys(obj, keys) { + // for (const key in obj) { + // if (keys.includes(key)) { + // delete obj[key]; + // } else if (typeof obj[key] === 'object' && obj[key] !== null) { + // recursivelyRemoveKeys(obj[key], keys); + // } + // } + // } + // const internalInterfaceKeys = ['env', 'help', 'action', 'default'] + // recursivelyRemoveKeys(interfaces, internalInterfaceKeys); + + // // Compose valid options + // const validOptions = interfaces.ParseServerOptions; + + // // Set sub options + // for (const interfaceName of Object.keys(interfaces)) { + // if (interfaceName === 'ParseServerOptions') { + // continue; + // } + + // // Get root key name from interface name + // let keyName = interfaceName.charAt(0).toLowerCase() + interfaceName.slice(1); + // keyName = keyName.slice(0, -"Options".length); + + // // Set sub keys + // validOptions[keyName] = interfaces[interfaceName]; + // } + + // // Find non matching keys + // function findNonMatchingKeys(a, b) { + // function compareObjects(objA, objB) { + // return Object.keys(objB).reduce((accumulator, key) => { + // const valA = objA[key]; + // const valB = objB[key]; + + // if (objA.hasOwnProperty(key) && typeof valA === 'object' && typeof valB === 'object') { + // const subObjectComparison = compareObjects(valA, valB); + // if (Object.keys(subObjectComparison).length > 0) { + // accumulator[key] = subObjectComparison; + // } + // } else if (!objA.hasOwnProperty(key)) { + // accumulator[key] = valB; + // } + // return accumulator; + // }, {}); + // } + + // return compareObjects(a, b); + // } + // const diff = findNonMatchingKeys(validOptions, options); + // END + Config.validateConfigKeyNames( Object.keys(ParseServerOptionDef), Object.keys(options), From 2297f4746742ad23be8e5ce8b62d8921f6961f6d Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:55:50 +0100 Subject: [PATCH 16/22] Update ParseServer.js --- src/ParseServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseServer.js b/src/ParseServer.js index a6362e52ae..e0bd2aa807 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -79,7 +79,7 @@ class ParseServer { // } // } // } - // const internalInterfaceKeys = ['env', 'help', 'action', 'default'] + // const internalInterfaceKeys = ['env', 'help', 'action', 'default', 'required']; // recursivelyRemoveKeys(interfaces, internalInterfaceKeys); // // Compose valid options From 753bbd86010d4022d0a88b1050bbf3e52cf3f523 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:15:58 +0200 Subject: [PATCH 17/22] refactor --- src/Config.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Config.js b/src/Config.js index 78386fcdb1..73ec24681e 100644 --- a/src/Config.js +++ b/src/Config.js @@ -68,8 +68,17 @@ export class Config { return serverConfiguration; } - static validateConfigKeyNames(actual, given, parent) { - const internalConfigVars = [ + /** + * Validates the keys that are set in the Parse Server options. + * + * @param {Array} allowedKeys The keys that are allowed in the Parse Server. + * @param {Array} setKeys The keys that are set in the Parse Server options. + * @param {String} rootKeyName The name of the root key that is being validated. + */ + static validateConfigKeyNames(allowedKeys, setKeys, rootKeyName) { + + // Internal keys that are not part of the official Parse Server options and should be ignored + const internalKeys = [ 'level', 'state', 'loggerController', @@ -92,7 +101,6 @@ export class Config { 'maintenanceKeyIpsStore', 'failedConfigKeyVerification', 'patternValidator', - 'applicationId', 'database', '_mount', @@ -111,14 +119,14 @@ export class Config { 'ops', ]; - for (const key of given) { - if (internalConfigVars.includes(key)) { + for (const key of setKeys) { + if (internalKeys.includes(key)) { continue; } - if (!actual.includes(key)) { + if (!allowedKeys.includes(key)) { Config.failedConfigKeyVerification = true; - console.warn(`Warning: The following key from ${parent} is not recognized: ${key}`); + console.warn(`Warning: The following key from ${rootKeyName} is not recognized: ${key}`); } } } From 2c058d8ddf0a2aec8a2e7fcbc2bf51125f45de68 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:21:25 +0200 Subject: [PATCH 18/22] remove centralized logic suggestion --- src/ParseServer.js | 59 ---------------------------------------------- 1 file changed, 59 deletions(-) diff --git a/src/ParseServer.js b/src/ParseServer.js index e0bd2aa807..ce2d60b6e3 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -49,7 +49,6 @@ import { ParseServerOptions as ParseServerOptionDef, LiveQueryServerOptions as LiveQueryServerOptionsDef, } from './Options/Definitions'; -import OptionsDefinitions from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -65,64 +64,6 @@ class ParseServer { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); - // START - // // Clone interfaces - // let interfaces = JSON.parse(JSON.stringify(OptionsDefinitions)); - - // // Remove internal interface keys - // function recursivelyRemoveKeys(obj, keys) { - // for (const key in obj) { - // if (keys.includes(key)) { - // delete obj[key]; - // } else if (typeof obj[key] === 'object' && obj[key] !== null) { - // recursivelyRemoveKeys(obj[key], keys); - // } - // } - // } - // const internalInterfaceKeys = ['env', 'help', 'action', 'default', 'required']; - // recursivelyRemoveKeys(interfaces, internalInterfaceKeys); - - // // Compose valid options - // const validOptions = interfaces.ParseServerOptions; - - // // Set sub options - // for (const interfaceName of Object.keys(interfaces)) { - // if (interfaceName === 'ParseServerOptions') { - // continue; - // } - - // // Get root key name from interface name - // let keyName = interfaceName.charAt(0).toLowerCase() + interfaceName.slice(1); - // keyName = keyName.slice(0, -"Options".length); - - // // Set sub keys - // validOptions[keyName] = interfaces[interfaceName]; - // } - - // // Find non matching keys - // function findNonMatchingKeys(a, b) { - // function compareObjects(objA, objB) { - // return Object.keys(objB).reduce((accumulator, key) => { - // const valA = objA[key]; - // const valB = objB[key]; - - // if (objA.hasOwnProperty(key) && typeof valA === 'object' && typeof valB === 'object') { - // const subObjectComparison = compareObjects(valA, valB); - // if (Object.keys(subObjectComparison).length > 0) { - // accumulator[key] = subObjectComparison; - // } - // } else if (!objA.hasOwnProperty(key)) { - // accumulator[key] = valB; - // } - // return accumulator; - // }, {}); - // } - - // return compareObjects(a, b); - // } - // const diff = findNonMatchingKeys(validOptions, options); - // END - Config.validateConfigKeyNames( Object.keys(ParseServerOptionDef), Object.keys(options), From 024c15405bec241dc5ece656c9af1a8403e239ed Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 6 Apr 2024 01:10:21 +0200 Subject: [PATCH 19/22] refactor --- spec/ParseConfigKey.spec.js | 42 +++---- src/Config.js | 234 +++++++++++++++++------------------- src/Controllers/index.js | 6 +- src/ParseServer.js | 12 +- 4 files changed, 131 insertions(+), 163 deletions(-) diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js index 4f0654f254..948069e5cb 100644 --- a/spec/ParseConfigKey.spec.js +++ b/spec/ParseConfigKey.spec.js @@ -10,7 +10,7 @@ describe('Config Keys', () => { spyOn(Config, 'put').and.callFake(() => { return {}; }); - // Spy on the console.warn method to capture warnings + // Spy on the console.error method to capture warnings spyOn(console, 'warn'); }); @@ -34,10 +34,10 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from ParseServerOptions is not recognized: masterKeyIPs' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' ); }); @@ -54,7 +54,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from SchemaOptions is not recognized: Strict' ); }); @@ -69,8 +69,8 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - // Check if console.warn was called with the expected message - expect(console.warn).toHaveBeenCalledWith( + // Check if console.error was called with the expected message + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' ); }); @@ -90,7 +90,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from RateLimitOptions is not recognized: IncludeInternalRequests' ); }); @@ -107,7 +107,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from SecurityOptions is not recognized: EnableCheck' ); }); @@ -124,7 +124,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from PagesOptions is not recognized: EnableRouter' ); }); @@ -145,7 +145,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from PagesRoute is not recognized: Handler' ); }); @@ -164,7 +164,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from PagesCustomUrlsOptions is not recognized: PasswordReset' ); }); @@ -181,7 +181,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from CustomPagesOptions is not recognized: InvalidLink' ); }); @@ -198,7 +198,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from LiveQueryOptions is not recognized: ClassNames' ); }); @@ -215,7 +215,7 @@ describe('Config Keys', () => { 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from IdempotencyOptions is not recognized: Ttl' ); }); @@ -233,7 +233,7 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from AccountLockoutOptions is not recognized: UnlockOnPasswordReset' ); }); @@ -249,7 +249,7 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from PasswordPolicyOptions is not recognized: MaxPasswordAge' ); }); @@ -265,7 +265,7 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from FileUploadOptions is not recognized: EnableForAnonymousUser' ); }); @@ -284,7 +284,7 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from DatabaseOptions is not recognized: SchemaCacheTtl' ); }); @@ -300,7 +300,7 @@ describe('Config Keys', () => { }).toThrowError( 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' ); - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from LogLevels is not recognized: CloudFunctionError' ); }); @@ -309,7 +309,7 @@ describe('Config Keys', () => { describe('Config Keys Checked on Server Start', () => { it('should throw incorrect key warning for LiveQueryServerOptions Config key', async () => { spyOn(Deprecator, 'scanParseServerOptions').and.callFake(doNothing); - // Spy on the console.warn method to capture warnings + // Spy on the console.error method to capture warnings spyOn(console, 'warn'); try { @@ -322,7 +322,7 @@ describe('Config Keys Checked on Server Start', () => { fail('Expected an error to be thrown'); } catch (err) { - expect(console.warn).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( 'Warning: The following key from LiveQueryServerOptions is not recognized: MasterKey' ); diff --git a/src/Config.js b/src/Config.js index 73ec24681e..ae4b72a6e2 100644 --- a/src/Config.js +++ b/src/Config.js @@ -73,9 +73,9 @@ export class Config { * * @param {Array} allowedKeys The keys that are allowed in the Parse Server. * @param {Array} setKeys The keys that are set in the Parse Server options. - * @param {String} rootKeyName The name of the root key that is being validated. + * @param {String} [keyPath] The path of the key that is being validated. */ - static validateConfigKeyNames(allowedKeys, setKeys, rootKeyName) { + static validateConfigKeyNames(allowedKeys, setKeys, keyPath) { // Internal keys that are not part of the official Parse Server options and should be ignored const internalKeys = [ @@ -126,7 +126,7 @@ export class Config { if (!allowedKeys.includes(key)) { Config.failedConfigKeyVerification = true; - console.warn(`Warning: The following key from ${rootKeyName} is not recognized: ${key}`); + console.error(`Error: Unknown Parse Server option '${keyPath ? keyPath + '.' : ''}${key}'.`); } } } @@ -214,11 +214,8 @@ export class Config { if (Object.prototype.toString.call(customPages) !== '[object Object]') { throw Error('Parse Server option customPages must be an object.'); } - Config.validateConfigKeyNames( - Object.keys(CustomPagesOptions), - Object.keys(customPages), - 'CustomPagesOptions' - ); + + Config.validateConfigKeyNames(Object.keys(CustomPagesOptions), Object.keys(customPages), 'customPages'); } static validateControllers({ @@ -271,11 +268,9 @@ export class Config { if (Object.prototype.toString.call(security) !== '[object Object]') { throw 'Parse Server option security must be an object.'; } - Config.validateConfigKeyNames( - Object.keys(SecurityOptions), - Object.keys(security), - 'SecurityOptions' - ); + + Config.validateConfigKeyNames(Object.keys(SecurityOptions), Object.keys(security), 'security'); + if (security.enableCheck === undefined) { security.enableCheck = SecurityOptions.enableCheck.default; } else if (!isBoolean(security.enableCheck)) { @@ -294,7 +289,8 @@ export class Config { if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } - Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'SchemaOptions'); + + Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'schema'); if (schema.definitions === undefined) { schema.definitions = SchemaOptions.definitions.default; @@ -338,7 +334,8 @@ export class Config { throw 'Parse Server option pages must be an object.'; } - Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages), 'PagesOptions'); + Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages), 'pages'); + if (pages.enableRouter === undefined) { pages.enableRouter = PagesOptions.enableRouter.default; } else if (!isBoolean(pages.enableRouter)) { @@ -387,11 +384,7 @@ export class Config { } else if (Object.prototype.toString.call(pages.customUrls) !== '[object Object]') { throw 'Parse Server option pages.customUrls must be an object.'; } else { - Config.validateConfigKeyNames( - Object.keys(PagesCustomUrlsOptions), - Object.keys(pages.customUrls), - 'PagesCustomUrlsOptions' - ); + Config.validateConfigKeyNames(Object.keys(PagesCustomUrlsOptions), Object.keys(pages.customUrls), 'pages.customUrls'); } if (pages.customRoutes === undefined) { @@ -400,7 +393,7 @@ export class Config { throw 'Parse Server option pages.customRoutes must be an array.'; } else { pages.customRoutes.forEach(item => { - Config.validateConfigKeyNames(Object.keys(PagesRoute), Object.keys(item), 'PagesRoute'); + Config.validateConfigKeyNames(Object.keys(PagesRoute), Object.keys(item), 'pages.customRoutes'); }); } } @@ -409,11 +402,9 @@ export class Config { if (!idempotencyOptions) { return; } - Config.validateConfigKeyNames( - Object.keys(IdempotencyOptions), - Object.keys(idempotencyOptions), - 'IdempotencyOptions' - ); + + Config.validateConfigKeyNames(Object.keys(IdempotencyOptions), Object.keys(idempotencyOptions), 'idempotencyOptions'); + if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { @@ -429,105 +420,103 @@ export class Config { } static validateAccountLockoutPolicy(accountLockout) { - if (accountLockout) { - Config.validateConfigKeyNames( - Object.keys(AccountLockoutOptions), - Object.keys(accountLockout), - 'AccountLockoutOptions' - ); - if ( - typeof accountLockout.duration !== 'number' || - accountLockout.duration <= 0 || - accountLockout.duration > 99999 - ) { - throw 'Account lockout duration should be greater than 0 and less than 100000'; - } + if (!accountLockout) { + return; + } - if ( - !Number.isInteger(accountLockout.threshold) || - accountLockout.threshold < 1 || - accountLockout.threshold > 999 - ) { - throw 'Account lockout threshold should be an integer greater than 0 and less than 1000'; - } + Config.validateConfigKeyNames(Object.keys(AccountLockoutOptions), Object.keys(accountLockout), 'accountLockout'); - if (accountLockout.unlockOnPasswordReset === undefined) { - accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default; - } else if (!isBoolean(accountLockout.unlockOnPasswordReset)) { - throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.'; - } + if ( + typeof accountLockout.duration !== 'number' || + accountLockout.duration <= 0 || + accountLockout.duration > 99999 + ) { + throw 'Account lockout duration should be greater than 0 and less than 100000'; + } + + if ( + !Number.isInteger(accountLockout.threshold) || + accountLockout.threshold < 1 || + accountLockout.threshold > 999 + ) { + throw 'Account lockout threshold should be an integer greater than 0 and less than 1000'; + } + + if (accountLockout.unlockOnPasswordReset === undefined) { + accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default; + } else if (!isBoolean(accountLockout.unlockOnPasswordReset)) { + throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.'; } } static validatePasswordPolicy(passwordPolicy) { - if (passwordPolicy) { - Config.validateConfigKeyNames( - Object.keys(PasswordPolicyOptions), - Object.keys(passwordPolicy), - 'PasswordPolicyOptions' - ); - if ( - passwordPolicy.maxPasswordAge !== undefined && - (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) - ) { - throw 'passwordPolicy.maxPasswordAge must be a positive number'; - } + if (!passwordPolicy) { + return; + } - if ( - passwordPolicy.resetTokenValidityDuration !== undefined && - (typeof passwordPolicy.resetTokenValidityDuration !== 'number' || - passwordPolicy.resetTokenValidityDuration <= 0) - ) { - throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; - } + Config.validateConfigKeyNames(Object.keys(PasswordPolicyOptions), Object.keys(passwordPolicy), 'passwordPolicy'); - if (passwordPolicy.validatorPattern) { - if (typeof passwordPolicy.validatorPattern === 'string') { - passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); - } else if (!(passwordPolicy.validatorPattern instanceof RegExp)) { - throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; - } - } + if ( + passwordPolicy.maxPasswordAge !== undefined && + (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) + ) { + throw 'passwordPolicy.maxPasswordAge must be a positive number'; + } - if ( - passwordPolicy.validatorCallback && - typeof passwordPolicy.validatorCallback !== 'function' - ) { - throw 'passwordPolicy.validatorCallback must be a function.'; - } + if ( + passwordPolicy.resetTokenValidityDuration !== undefined && + (typeof passwordPolicy.resetTokenValidityDuration !== 'number' || + passwordPolicy.resetTokenValidityDuration <= 0) + ) { + throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; + } - if ( - passwordPolicy.doNotAllowUsername && - typeof passwordPolicy.doNotAllowUsername !== 'boolean' - ) { - throw 'passwordPolicy.doNotAllowUsername must be a boolean value.'; + if (passwordPolicy.validatorPattern) { + if (typeof passwordPolicy.validatorPattern === 'string') { + passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); + } else if (!(passwordPolicy.validatorPattern instanceof RegExp)) { + throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; } + } - if ( - passwordPolicy.maxPasswordHistory && - (!Number.isInteger(passwordPolicy.maxPasswordHistory) || - passwordPolicy.maxPasswordHistory <= 0 || - passwordPolicy.maxPasswordHistory > 20) - ) { - throw 'passwordPolicy.maxPasswordHistory must be an integer ranging 0 - 20'; - } + if ( + passwordPolicy.validatorCallback && + typeof passwordPolicy.validatorCallback !== 'function' + ) { + throw 'passwordPolicy.validatorCallback must be a function.'; + } - if ( - passwordPolicy.resetTokenReuseIfValid && - typeof passwordPolicy.resetTokenReuseIfValid !== 'boolean' - ) { - throw 'resetTokenReuseIfValid must be a boolean value'; - } - if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) { - throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration'; - } + if ( + passwordPolicy.doNotAllowUsername && + typeof passwordPolicy.doNotAllowUsername !== 'boolean' + ) { + throw 'passwordPolicy.doNotAllowUsername must be a boolean value.'; + } - if ( - passwordPolicy.resetPasswordSuccessOnInvalidEmail && - typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean' - ) { - throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value'; - } + if ( + passwordPolicy.maxPasswordHistory && + (!Number.isInteger(passwordPolicy.maxPasswordHistory) || + passwordPolicy.maxPasswordHistory <= 0 || + passwordPolicy.maxPasswordHistory > 20) + ) { + throw 'passwordPolicy.maxPasswordHistory must be an integer ranging 0 - 20'; + } + + if ( + passwordPolicy.resetTokenReuseIfValid && + typeof passwordPolicy.resetTokenReuseIfValid !== 'boolean' + ) { + throw 'resetTokenReuseIfValid must be a boolean value'; + } + if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) { + throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration'; + } + + if ( + passwordPolicy.resetPasswordSuccessOnInvalidEmail && + typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean' + ) { + throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value'; } } @@ -583,11 +572,8 @@ export class Config { throw e; } - Config.validateConfigKeyNames( - Object.keys(FileUploadOptions), - Object.keys(fileUpload), - 'FileUploadOptions' - ); + Config.validateConfigKeyNames(Object.keys(FileUploadOptions), Object.keys(fileUpload), 'fileUpload'); + if (fileUpload.enableForAnonymousUser === undefined) { fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { @@ -678,7 +664,8 @@ export class Config { } static validateLogLevels(logLevels) { - Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels), 'LogLevels'); + Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels), 'logLevels'); + for (const key of Object.keys(LogLevels)) { if (logLevels[key]) { if (validLogLevels.indexOf(logLevels[key]) === -1) { @@ -698,11 +685,8 @@ export class Config { if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') { throw `databaseOptions must be an object`; } - Config.validateConfigKeyNames( - Object.keys(DatabaseOptions), - Object.keys(databaseOptions), - 'DatabaseOptions' - ); + + Config.validateConfigKeyNames(Object.keys(DatabaseOptions), Object.keys(databaseOptions), 'databaseOptions'); if (databaseOptions.enableSchemaHooks === undefined) { databaseOptions.enableSchemaHooks = DatabaseOptions.enableSchemaHooks.default; @@ -733,11 +717,7 @@ export class Config { throw `rateLimit must be an array of objects`; } - Config.validateConfigKeyNames( - Object.keys(RateLimitOptions), - Object.keys(option), - 'RateLimitOptions' - ); + Config.validateConfigKeyNames(Object.keys(RateLimitOptions), Object.keys(option), 'rateLimit'); if (option.requestPath == null) { throw `rateLimit.requestPath must be defined`; diff --git a/src/Controllers/index.js b/src/Controllers/index.js index cf5f8ba16b..ca1592ce4b 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -150,11 +150,7 @@ export function getAnalyticsController(options: ParseServerOptions): AnalyticsCo export function getLiveQueryController(options: ParseServerOptions): LiveQueryController { if (options.liveQuery) { if (Object.prototype.toString.call(options.liveQuery) === '[object Object]') { - Config.validateConfigKeyNames( - Object.keys(LiveQueryOptions), - Object.keys(options.liveQuery), - 'LiveQueryOptions' - ); + Config.validateConfigKeyNames(Object.keys(LiveQueryOptions), Object.keys(options.liveQuery), 'liveQuery'); } } return new LiveQueryController(options.liveQuery); diff --git a/src/ParseServer.js b/src/ParseServer.js index ce2d60b6e3..5015f12f71 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -64,21 +64,13 @@ class ParseServer { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); - Config.validateConfigKeyNames( - Object.keys(ParseServerOptionDef), - Object.keys(options), - 'ParseServerOptions' - ); + Config.validateConfigKeyNames(Object.keys(ParseServerOptionDef), Object.keys(options)); if ( options.liveQueryServerOptions && Object.prototype.toString.call(options.liveQueryServerOptions) === '[object Object]' ) { - Config.validateConfigKeyNames( - Object.keys(LiveQueryServerOptionsDef), - Object.keys(options.liveQueryServerOptions), - 'LiveQueryServerOptions' - ); + Config.validateConfigKeyNames(Object.keys(LiveQueryServerOptionsDef), Object.keys(options.liveQueryServerOptions), 'liveQueryServerOptions'); } // Set option defaults From b25cfef80ef0e91101fcd647791391c2ccdcfb03 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:33:09 +0000 Subject: [PATCH 20/22] refactor: v2 --- resources/buildConfigDefinitions.js | 17 ++ spec/ParseConfigKey.spec.js | 358 +++------------------------- src/Config.js | 314 ++++++++---------------- src/Controllers/index.js | 7 - src/Options/Definitions.js | 15 ++ src/ParseServer.js | 65 +++-- 6 files changed, 224 insertions(+), 552 deletions(-) diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index 0be6e0085d..72a37cb912 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -254,6 +254,23 @@ function inject(t, list) { if (action) { props.push(t.objectProperty(t.stringLiteral('action'), action)); } + + if (t.isGenericTypeAnnotation(elt)) { + if (elt.typeAnnotation.id.name in nestedOptionEnvPrefix) { + props.push( + t.objectProperty(t.stringLiteral('type'), t.stringLiteral(elt.typeAnnotation.id.name)) + ); + } + } else if (t.isArrayTypeAnnotation(elt)) { + const elementType = elt.typeAnnotation.elementType; + if (t.isGenericTypeAnnotation(elementType)) { + if (elementType.id.name in nestedOptionEnvPrefix) { + props.push( + t.objectProperty(t.stringLiteral('type'), t.stringLiteral(elementType.id.name + '[]')) + ); + } + } + } if (elt.defaultValue) { let parsedValue = parseDefaultValue(elt, elt.defaultValue, t); if (!parsedValue) { diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js index 948069e5cb..84b2fc6e2f 100644 --- a/spec/ParseConfigKey.spec.js +++ b/spec/ParseConfigKey.spec.js @@ -1,334 +1,52 @@ -const ParseServer = require('../lib/index').ParseServer; const Config = require('../lib/Config'); -const Deprecator = require('../lib/Deprecator/Deprecator'); - -const doNothing = () => {}; +const ParseServer = require('../lib/index').ParseServer; describe('Config Keys', () => { - beforeEach(() => { - spyOn(Deprecator, 'scanParseServerOptions').and.callFake(doNothing); - spyOn(Config, 'put').and.callFake(() => { - return {}; + const tests = [ + { + name: 'Invalid Root Keys', + options: { unknow: 'val', masterKeyIPs: '' }, + error: 'unknow, masterKeyIPs', + }, + { name: 'Invalid Schema Keys', options: { schema: { Strict: 'val' } }, error: 'schema.Strict' }, + { + name: 'Invalid Pages Keys', + options: { pages: { customUrls: { EmailVerificationSendFail: 'val' } } }, + error: 'pages.customUrls.EmailVerificationSendFail', + }, + { + name: 'Invalid LiveQueryServerOptions Keys', + options: { liveQueryServerOptions: { MasterKey: 'value' } }, + error: 'liveQueryServerOptions.MasterKey', + }, + { + name: 'Invalid RateLimit Keys - Array Item', + options: { rateLimit: [{ RequestPath: '' }, { RequestTimeWindow: '' }] }, + error: 'rateLimit[0].RequestPath, rateLimit[1].RequestTimeWindow', + }, + ]; + + tests.forEach(test => { + it(test.name, async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callThrough(); + spyOn(Config, 'validateOptions').and.callFake(() => {}); + + new ParseServer({ + ...defaultConfiguration, + ...test.options, + }); + expect(logger.error).toHaveBeenCalledWith(`Invalid Option Keys Found: ${test.error}`); }); - // Spy on the console.error method to capture warnings - spyOn(console, 'warn'); }); - it('should run fine', () => { + it('should run fine', async () => { try { - new ParseServer({ + await reconfigureServer({ ...defaultConfiguration, }); } catch (err) { fail('Should run without error'); } }); - - it('should throw 2 warnings for incorrect key names in Config', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - unknownKeyName: 'unknownValue', // invalid key - masterKeyIPs: '', // invalid key - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from ParseServerOptions is not recognized: masterKeyIPs' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' - ); - }); - - it('should throw incorrect key warning for Schema Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - schema: { - Strict: true, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from SchemaOptions is not recognized: Strict' - ); - }); - - it('should throw incorrect key warning for ParseServer Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - unknownKeyName: 'unknownValue', // invalid key - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - // Check if console.error was called with the expected message - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from ParseServerOptions is not recognized: unknownKeyName' - ); - }); - - it('should throw incorrect key warning for RateLimitOption Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - rateLimit: { - requestPath: '', - requestTimeWindow: 10, - requestCount: 10, - IncludeInternalRequests: false, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from RateLimitOptions is not recognized: IncludeInternalRequests' - ); - }); - - it('should throw incorrect key warning for Security Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - security: { - EnableCheck: true, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from SecurityOptions is not recognized: EnableCheck' - ); - }); - - it('should throw incorrect key warning for Pages Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - pages: { - EnableRouter: true, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from PagesOptions is not recognized: EnableRouter' - ); - }); - - it('should throw incorrect key warning for PagesRoute Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - pages: { - customRoutes: [ - { - Handler: () => {}, // invalid key - }, - ], - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from PagesRoute is not recognized: Handler' - ); - }); - - it('should throw incorrect key warning for PagesCustomUrls Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - pages: { - customUrls: { - PasswordReset: '', // invalid key - }, - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from PagesCustomUrlsOptions is not recognized: PasswordReset' - ); - }); - - it('should throw incorrect key warning for customPagesOption Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - customPages: { - InvalidLink: '', // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from CustomPagesOptions is not recognized: InvalidLink' - ); - }); - - it('should throw incorrect key warning for LiveQueryOptions Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - liveQuery: { - ClassNames: '', // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from LiveQueryOptions is not recognized: ClassNames' - ); - }); - - it('should throw incorrect key warning for IdempotencyOptions Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - idempotencyOptions: { - Ttl: 10, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from IdempotencyOptions is not recognized: Ttl' - ); - }); - - it('should throw incorrect key warning for AccountLockoutOptions Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - accountLockout: { - duration: 10, - threshold: 10, - UnlockOnPasswordReset: false, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from AccountLockoutOptions is not recognized: UnlockOnPasswordReset' - ); - }); - - it('should throw incorrect key warning for PasswordPolicyOptions Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - passwordPolicy: { - MaxPasswordAge: 10, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from PasswordPolicyOptions is not recognized: MaxPasswordAge' - ); - }); - - it('should throw incorrect key warning for FileUploadOptions Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - fileUpload: { - EnableForAnonymousUser: false, // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from FileUploadOptions is not recognized: EnableForAnonymousUser' - ); - }); - - it('should throw incorrect key warning for DatabaseOptions Config key', () => { - const dbConfig = { - ...defaultConfiguration, - databaseOptions: { - SchemaCacheTtl: 10, // invalid key - }, - }; - delete dbConfig.databaseAdapter; - - expect(() => { - new ParseServer(dbConfig); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from DatabaseOptions is not recognized: SchemaCacheTtl' - ); - }); - - it('should throw incorrect key warning for LogLevels Config key', () => { - expect(() => { - new ParseServer({ - ...defaultConfiguration, - logLevels: { - CloudFunctionError: 'error', // invalid key - }, - }); - }).toThrowError( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from LogLevels is not recognized: CloudFunctionError' - ); - }); -}); - -describe('Config Keys Checked on Server Start', () => { - it('should throw incorrect key warning for LiveQueryServerOptions Config key', async () => { - spyOn(Deprecator, 'scanParseServerOptions').and.callFake(doNothing); - // Spy on the console.error method to capture warnings - spyOn(console, 'warn'); - - try { - await ParseServer.startApp({ - ...defaultConfiguration, - liveQueryServerOptions: { - MasterKey: '', // invalid key - }, - }); - - fail('Expected an error to be thrown'); - } catch (err) { - expect(console.error).toHaveBeenCalledWith( - 'Warning: The following key from LiveQueryServerOptions is not recognized: MasterKey' - ); - - expect(err.message).toBe( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - } - }); }); diff --git a/src/Config.js b/src/Config.js index ae4b72a6e2..115b387944 100644 --- a/src/Config.js +++ b/src/Config.js @@ -10,7 +10,6 @@ import { logLevels as validLogLevels } from './Controllers/LoggerController'; import { version } from '../package.json'; import { AccountLockoutOptions, - PagesRoute, DatabaseOptions, FileUploadOptions, IdempotencyOptions, @@ -19,10 +18,6 @@ import { ParseServerOptions, SchemaOptions, SecurityOptions, - PasswordPolicyOptions, - RateLimitOptions, - CustomPagesOptions, - PagesCustomUrlsOptions, } from './Options/Definitions'; import ParseServer from './cloud-code/Parse.Server'; @@ -68,101 +63,36 @@ export class Config { return serverConfiguration; } - /** - * Validates the keys that are set in the Parse Server options. - * - * @param {Array} allowedKeys The keys that are allowed in the Parse Server. - * @param {Array} setKeys The keys that are set in the Parse Server options. - * @param {String} [keyPath] The path of the key that is being validated. - */ - static validateConfigKeyNames(allowedKeys, setKeys, keyPath) { - - // Internal keys that are not part of the official Parse Server options and should be ignored - const internalKeys = [ - 'level', - 'state', - 'loggerController', - 'filesController', - 'userController', - 'pushController', - 'hasPushScheduledSupport', - 'hasPushSupport', - 'pushWorker', - 'pushControllerQueue', - 'analyticsController', - 'cacheController', - 'parseGraphQLController', - 'liveQueryController', - 'databaseController', - 'hooksController', - 'authDataManager', - 'schemaCache', - 'masterKeyIpsStore', - 'maintenanceKeyIpsStore', - 'failedConfigKeyVerification', - 'patternValidator', - 'applicationId', - 'database', - '_mount', - 'generateSessionExpiresAt', - 'generateEmailVerifyTokenExpiresAt', - 'version', - 'RateLimitZone', - 'setIdempotencyFunction', - - // From test files - 'retryWrites', - 'customIdSize', - 'path', - 'exampleKey', - 'websocketTimeout', - 'ops', - ]; - - for (const key of setKeys) { - if (internalKeys.includes(key)) { - continue; - } - - if (!allowedKeys.includes(key)) { - Config.failedConfigKeyVerification = true; - console.error(`Error: Unknown Parse Server option '${keyPath ? keyPath + '.' : ''}${key}'.`); - } - } - } - - static validateOptions(options) { - const { - customPages, - publicServerURL, - revokeSessionOnPasswordReset, - expireInactiveSessions, - sessionLength, - defaultLimit, - maxLimit, - accountLockout, - passwordPolicy, - masterKeyIps, - masterKey, - maintenanceKey, - maintenanceKeyIps, - readOnlyMasterKey, - allowHeaders, - idempotencyOptions, - fileUpload, - pages, - security, - enforcePrivateUsers, - schema, - requestKeywordDenylist, - allowExpiredAuthDataToken, - logLevels, - rateLimit, - databaseOptions, - extendSessionOnUse, - allowClientClassCreation, - } = options; - + static validateOptions({ + customPages, + publicServerURL, + revokeSessionOnPasswordReset, + expireInactiveSessions, + sessionLength, + defaultLimit, + maxLimit, + accountLockout, + passwordPolicy, + masterKeyIps, + masterKey, + maintenanceKey, + maintenanceKeyIps, + readOnlyMasterKey, + allowHeaders, + idempotencyOptions, + fileUpload, + pages, + security, + enforcePrivateUsers, + schema, + requestKeywordDenylist, + allowExpiredAuthDataToken, + logLevels, + rateLimit, + databaseOptions, + extendSessionOnUse, + allowClientClassCreation, + }) { if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); } @@ -214,8 +144,6 @@ export class Config { if (Object.prototype.toString.call(customPages) !== '[object Object]') { throw Error('Parse Server option customPages must be an object.'); } - - Config.validateConfigKeyNames(Object.keys(CustomPagesOptions), Object.keys(customPages), 'customPages'); } static validateControllers({ @@ -268,9 +196,6 @@ export class Config { if (Object.prototype.toString.call(security) !== '[object Object]') { throw 'Parse Server option security must be an object.'; } - - Config.validateConfigKeyNames(Object.keys(SecurityOptions), Object.keys(security), 'security'); - if (security.enableCheck === undefined) { security.enableCheck = SecurityOptions.enableCheck.default; } else if (!isBoolean(security.enableCheck)) { @@ -290,8 +215,6 @@ export class Config { throw 'Parse Server option schema must be an object.'; } - Config.validateConfigKeyNames(Object.keys(SchemaOptions), Object.keys(schema), 'schema'); - if (schema.definitions === undefined) { schema.definitions = SchemaOptions.definitions.default; } else if (!Array.isArray(schema.definitions)) { @@ -334,8 +257,6 @@ export class Config { throw 'Parse Server option pages must be an object.'; } - Config.validateConfigKeyNames(Object.keys(PagesOptions), Object.keys(pages), 'pages'); - if (pages.enableRouter === undefined) { pages.enableRouter = PagesOptions.enableRouter.default; } else if (!isBoolean(pages.enableRouter)) { @@ -383,18 +304,12 @@ export class Config { pages.customUrls = PagesOptions.customUrls.default; } else if (Object.prototype.toString.call(pages.customUrls) !== '[object Object]') { throw 'Parse Server option pages.customUrls must be an object.'; - } else { - Config.validateConfigKeyNames(Object.keys(PagesCustomUrlsOptions), Object.keys(pages.customUrls), 'pages.customUrls'); } if (pages.customRoutes === undefined) { pages.customRoutes = PagesOptions.customRoutes.default; } else if (!(pages.customRoutes instanceof Array)) { throw 'Parse Server option pages.customRoutes must be an array.'; - } else { - pages.customRoutes.forEach(item => { - Config.validateConfigKeyNames(Object.keys(PagesRoute), Object.keys(item), 'pages.customRoutes'); - }); } } @@ -402,9 +317,6 @@ export class Config { if (!idempotencyOptions) { return; } - - Config.validateConfigKeyNames(Object.keys(IdempotencyOptions), Object.keys(idempotencyOptions), 'idempotencyOptions'); - if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { @@ -420,103 +332,95 @@ export class Config { } static validateAccountLockoutPolicy(accountLockout) { - if (!accountLockout) { - return; - } - - Config.validateConfigKeyNames(Object.keys(AccountLockoutOptions), Object.keys(accountLockout), 'accountLockout'); - - if ( - typeof accountLockout.duration !== 'number' || - accountLockout.duration <= 0 || - accountLockout.duration > 99999 - ) { - throw 'Account lockout duration should be greater than 0 and less than 100000'; - } + if (accountLockout) { + if ( + typeof accountLockout.duration !== 'number' || + accountLockout.duration <= 0 || + accountLockout.duration > 99999 + ) { + throw 'Account lockout duration should be greater than 0 and less than 100000'; + } - if ( - !Number.isInteger(accountLockout.threshold) || - accountLockout.threshold < 1 || - accountLockout.threshold > 999 - ) { - throw 'Account lockout threshold should be an integer greater than 0 and less than 1000'; - } + if ( + !Number.isInteger(accountLockout.threshold) || + accountLockout.threshold < 1 || + accountLockout.threshold > 999 + ) { + throw 'Account lockout threshold should be an integer greater than 0 and less than 1000'; + } - if (accountLockout.unlockOnPasswordReset === undefined) { - accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default; - } else if (!isBoolean(accountLockout.unlockOnPasswordReset)) { - throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.'; + if (accountLockout.unlockOnPasswordReset === undefined) { + accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default; + } else if (!isBoolean(accountLockout.unlockOnPasswordReset)) { + throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.'; + } } } static validatePasswordPolicy(passwordPolicy) { - if (!passwordPolicy) { - return; - } - - Config.validateConfigKeyNames(Object.keys(PasswordPolicyOptions), Object.keys(passwordPolicy), 'passwordPolicy'); - - if ( - passwordPolicy.maxPasswordAge !== undefined && - (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) - ) { - throw 'passwordPolicy.maxPasswordAge must be a positive number'; - } + if (passwordPolicy) { + if ( + passwordPolicy.maxPasswordAge !== undefined && + (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) + ) { + throw 'passwordPolicy.maxPasswordAge must be a positive number'; + } - if ( - passwordPolicy.resetTokenValidityDuration !== undefined && - (typeof passwordPolicy.resetTokenValidityDuration !== 'number' || - passwordPolicy.resetTokenValidityDuration <= 0) - ) { - throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; - } + if ( + passwordPolicy.resetTokenValidityDuration !== undefined && + (typeof passwordPolicy.resetTokenValidityDuration !== 'number' || + passwordPolicy.resetTokenValidityDuration <= 0) + ) { + throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; + } - if (passwordPolicy.validatorPattern) { - if (typeof passwordPolicy.validatorPattern === 'string') { - passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); - } else if (!(passwordPolicy.validatorPattern instanceof RegExp)) { - throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; + if (passwordPolicy.validatorPattern) { + if (typeof passwordPolicy.validatorPattern === 'string') { + passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); + } else if (!(passwordPolicy.validatorPattern instanceof RegExp)) { + throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; + } } - } - if ( - passwordPolicy.validatorCallback && - typeof passwordPolicy.validatorCallback !== 'function' - ) { - throw 'passwordPolicy.validatorCallback must be a function.'; - } + if ( + passwordPolicy.validatorCallback && + typeof passwordPolicy.validatorCallback !== 'function' + ) { + throw 'passwordPolicy.validatorCallback must be a function.'; + } - if ( - passwordPolicy.doNotAllowUsername && - typeof passwordPolicy.doNotAllowUsername !== 'boolean' - ) { - throw 'passwordPolicy.doNotAllowUsername must be a boolean value.'; - } + if ( + passwordPolicy.doNotAllowUsername && + typeof passwordPolicy.doNotAllowUsername !== 'boolean' + ) { + throw 'passwordPolicy.doNotAllowUsername must be a boolean value.'; + } - if ( - passwordPolicy.maxPasswordHistory && - (!Number.isInteger(passwordPolicy.maxPasswordHistory) || - passwordPolicy.maxPasswordHistory <= 0 || - passwordPolicy.maxPasswordHistory > 20) - ) { - throw 'passwordPolicy.maxPasswordHistory must be an integer ranging 0 - 20'; - } + if ( + passwordPolicy.maxPasswordHistory && + (!Number.isInteger(passwordPolicy.maxPasswordHistory) || + passwordPolicy.maxPasswordHistory <= 0 || + passwordPolicy.maxPasswordHistory > 20) + ) { + throw 'passwordPolicy.maxPasswordHistory must be an integer ranging 0 - 20'; + } - if ( - passwordPolicy.resetTokenReuseIfValid && - typeof passwordPolicy.resetTokenReuseIfValid !== 'boolean' - ) { - throw 'resetTokenReuseIfValid must be a boolean value'; - } - if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) { - throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration'; - } + if ( + passwordPolicy.resetTokenReuseIfValid && + typeof passwordPolicy.resetTokenReuseIfValid !== 'boolean' + ) { + throw 'resetTokenReuseIfValid must be a boolean value'; + } + if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) { + throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration'; + } - if ( - passwordPolicy.resetPasswordSuccessOnInvalidEmail && - typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean' - ) { - throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value'; + if ( + passwordPolicy.resetPasswordSuccessOnInvalidEmail && + typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean' + ) { + throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value'; + } } } @@ -572,8 +476,6 @@ export class Config { throw e; } - Config.validateConfigKeyNames(Object.keys(FileUploadOptions), Object.keys(fileUpload), 'fileUpload'); - if (fileUpload.enableForAnonymousUser === undefined) { fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { @@ -664,8 +566,6 @@ export class Config { } static validateLogLevels(logLevels) { - Config.validateConfigKeyNames(Object.keys(LogLevels), Object.keys(logLevels), 'logLevels'); - for (const key of Object.keys(LogLevels)) { if (logLevels[key]) { if (validLogLevels.indexOf(logLevels[key]) === -1) { @@ -686,8 +586,6 @@ export class Config { throw `databaseOptions must be an object`; } - Config.validateConfigKeyNames(Object.keys(DatabaseOptions), Object.keys(databaseOptions), 'databaseOptions'); - if (databaseOptions.enableSchemaHooks === undefined) { databaseOptions.enableSchemaHooks = DatabaseOptions.enableSchemaHooks.default; } else if (typeof databaseOptions.enableSchemaHooks !== 'boolean') { @@ -717,8 +615,6 @@ export class Config { throw `rateLimit must be an array of objects`; } - Config.validateConfigKeyNames(Object.keys(RateLimitOptions), Object.keys(option), 'rateLimit'); - if (option.requestPath == null) { throw `rateLimit.requestPath must be defined`; } diff --git a/src/Controllers/index.js b/src/Controllers/index.js index ca1592ce4b..0a9b3db57d 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -1,6 +1,5 @@ import authDataManager from '../Adapters/Auth'; import { ParseServerOptions } from '../Options'; -import { LiveQueryOptions } from '../Options/Definitions'; import { loadAdapter } from '../Adapters/AdapterLoader'; import defaults from '../defaults'; // Controllers @@ -26,7 +25,6 @@ import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorage import ParsePushAdapter from '@parse/push-adapter'; import ParseGraphQLController from './ParseGraphQLController'; import SchemaCache from '../Adapters/Cache/SchemaCache'; -import Config from '../Config'; export function getControllers(options: ParseServerOptions) { const loggerController = getLoggerController(options); @@ -148,11 +146,6 @@ export function getAnalyticsController(options: ParseServerOptions): AnalyticsCo } export function getLiveQueryController(options: ParseServerOptions): LiveQueryController { - if (options.liveQuery) { - if (Object.prototype.toString.call(options.liveQuery) === '[object Object]') { - Config.validateConfigKeyNames(Object.keys(LiveQueryOptions), Object.keys(options.liveQuery), 'liveQuery'); - } - } return new LiveQueryController(options.liveQuery); } diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index ee1137b7ea..27baac2258 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -54,6 +54,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_ACCOUNT_LOCKOUT', help: 'The account lockout policy for failed login attempts.', action: parsers.objectParser, + type: 'AccountLockoutOptions', }, allowClientClassCreation: { env: 'PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION', @@ -157,6 +158,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_CUSTOM_PAGES', help: 'custom pages for password validation and reset', action: parsers.objectParser, + type: 'CustomPagesOptions', default: {}, }, databaseAdapter: { @@ -169,6 +171,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_DATABASE_OPTIONS', help: 'Options to pass to the database client', action: parsers.objectParser, + type: 'DatabaseOptions', }, databaseURI: { env: 'PARSE_SERVER_DATABASE_URI', @@ -273,6 +276,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_FILE_UPLOAD_OPTIONS', help: 'Options for file uploads', action: parsers.objectParser, + type: 'FileUploadOptions', default: {}, }, graphQLPath: { @@ -294,6 +298,7 @@ module.exports.ParseServerOptions = { help: 'Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.', action: parsers.objectParser, + type: 'IdempotencyOptions', default: {}, }, javascriptKey: { @@ -309,11 +314,13 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_LIVE_QUERY', help: "parse-server's LiveQuery configuration object", action: parsers.objectParser, + type: 'LiveQueryOptions', }, liveQueryServerOptions: { env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS', help: 'Live query server configuration options (will start the liveQuery server)', action: parsers.objectParser, + type: 'LiveQueryServerOptions', }, loggerAdapter: { env: 'PARSE_SERVER_LOGGER_ADAPTER', @@ -328,6 +335,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_LOG_LEVELS', help: '(Optional) Overrides the log levels used internally by Parse Server to log events.', action: parsers.objectParser, + type: 'LogLevels', default: {}, }, logsFolder: { @@ -408,12 +416,14 @@ module.exports.ParseServerOptions = { help: 'The options for pages such as password reset and email verification. Caution, this is an experimental feature that may not be appropriate for production.', action: parsers.objectParser, + type: 'PagesOptions', default: {}, }, passwordPolicy: { env: 'PARSE_SERVER_PASSWORD_POLICY', help: 'The password policy for enforcing password related rules.', action: parsers.objectParser, + type: 'PasswordPolicyOptions', }, playgroundPath: { env: 'PARSE_SERVER_PLAYGROUND_PATH', @@ -471,6 +481,7 @@ module.exports.ParseServerOptions = { help: "Options to limit repeated requests to Parse Server APIs. This can be used to protect sensitive endpoints such as `/requestPasswordReset` from brute-force attacks or Parse Server as a whole from denial-of-service (DoS) attacks.

\u2139\uFE0F Mind the following limitations:
- rate limits applied per IP address; this limits protection against distributed denial-of-service (DDoS) attacks where many requests are coming from various IP addresses
- if multiple Parse Server instances are behind a load balancer or ran in a cluster, each instance will calculate it's own request rates, independent from other instances; this limits the applicability of this feature when using a load balancer and another rate limiting solution that takes requests across all instances into account may be more suitable
- this feature provides basic protection against denial-of-service attacks, but a more sophisticated solution works earlier in the request flow and prevents a malicious requests to even reach a server instance; it's therefore recommended to implement a solution according to architecture and user case.", action: parsers.arrayParser, + type: 'RateLimitOptions[]', default: [], }, readOnlyMasterKey: { @@ -516,11 +527,13 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_SCHEMA', help: 'Defined schema', action: parsers.objectParser, + type: 'SchemaOptions', }, security: { env: 'PARSE_SERVER_SECURITY', help: 'The security options to identify and report weak security settings.', action: parsers.objectParser, + type: 'SecurityOptions', default: {}, }, sendUserEmailVerification: { @@ -665,12 +678,14 @@ module.exports.PagesOptions = { env: 'PARSE_SERVER_PAGES_CUSTOM_ROUTES', help: 'The custom routes.', action: parsers.arrayParser, + type: 'PagesRoute[]', default: [], }, customUrls: { env: 'PARSE_SERVER_PAGES_CUSTOM_URLS', help: 'The URLs to the custom pages.', action: parsers.objectParser, + type: 'PagesCustomUrlsOptions', default: {}, }, enableLocalization: { diff --git a/src/ParseServer.js b/src/ParseServer.js index 5015f12f71..e8c7078a90 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,10 +45,7 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; -import { - ParseServerOptions as ParseServerOptionDef, - LiveQueryServerOptions as LiveQueryServerOptionsDef, -} from './Options/Definitions'; +import OptionsDefinitions from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -64,13 +61,55 @@ class ParseServer { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); - Config.validateConfigKeyNames(Object.keys(ParseServerOptionDef), Object.keys(options)); + const interfaces = JSON.parse(JSON.stringify(OptionsDefinitions)); - if ( - options.liveQueryServerOptions && - Object.prototype.toString.call(options.liveQueryServerOptions) === '[object Object]' - ) { - Config.validateConfigKeyNames(Object.keys(LiveQueryServerOptionsDef), Object.keys(options.liveQueryServerOptions), 'liveQueryServerOptions'); + function getValidObject(root) { + const result = {}; + for (const key in root) { + if (Object.prototype.hasOwnProperty.call(root[key], 'type')) { + if (root[key].type.endsWith('[]')) { + result[key] = [getValidObject(interfaces[root[key].type.slice(0, -2)])]; + } else { + result[key] = getValidObject(interfaces[root[key].type]); + } + } else { + result[key] = ''; + } + } + return result; + } + + const optionsBlueprint = getValidObject(interfaces['ParseServerOptions']); + + function validateKeyNames(original, ref, name = '') { + let result = []; + const prefix = name + (name !== '' ? '.' : ''); + for (const key in original) { + if (!Object.prototype.hasOwnProperty.call(ref, key)) { + result.push(prefix + key); + } else { + if (ref[key] === '') continue; + let res = []; + if (Array.isArray(original[key]) && Array.isArray(ref[key])) { + const type = ref[key][0]; + original[key].forEach((item, idx) => { + if (typeof item === 'object' && item !== null) { + res = res.concat(validateKeyNames(item, type, prefix + key + `[${idx}]`)); + } + }); + } else if (typeof original[key] === 'object' && typeof ref[key] === 'object') { + res = validateKeyNames(original[key], ref[key], prefix + key); + } + result = result.concat(res); + } + } + return result; + } + + const diff = validateKeyNames(options, optionsBlueprint); + if (diff.length > 0) { + const logger = logging.logger; + logger.error(`Invalid Option Keys Found: ${diff.join(', ')}`); } // Set option defaults @@ -86,12 +125,6 @@ class ParseServer { Parse.serverURL = serverURL; Config.validateOptions(options); const allControllers = controllers.getControllers(options); - if (Config.failedConfigKeyVerification) { - delete Config.failedConfigKeyVerification; - throw new Error( - 'Unknown key(s) found in Parse Server configuration, see other warning messages for details.' - ); - } options.state = 'initialized'; this.config = Config.put(Object.assign({}, options, allControllers)); From 5756ccab7f9fdbc2a0b809b3d722b4181c41baf9 Mon Sep 17 00:00:00 2001 From: Vivek Joshi <37882929+vivekjoshi556@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:34:50 +0000 Subject: [PATCH 21/22] docs: reverting contributing guidelines --- CONTRIBUTING.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d55ef9f8bc..270b66e385 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,10 +407,7 @@ Introducing new [Parse Server configuration][config] parameters requires the fol For example, take a look at the existing Parse Server `security` parameter. It is a parameter group, because it has multiple sub-parameter such as `checkGroups`. Its interface is defined in [index.js][config-index] as `export interface SecurityOptions`. Therefore, the value to add to `nestedOptionTypes` would be `SecurityOptions`, the value to add to `nestedOptionEnvPrefix` would be `PARSE_SERVER_SECURITY_`. 3. Execute `npm run definitions` to automatically create the definitions in [/src/Options/Definitions.js][config-def] and [/src/Options/docs.js][config-docs]. -4. Add parameter validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js): - - Validate parameter name by using `validateConfigKeyNames`. - - Validate parameter value by checking the value type and the value itself (e.g. min, max bounds for numeric types, regex parsing for string types, or object parsing for object types). - For reference see existing parameter validations. +4. Add parameter value validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js). 5. Add test cases to ensure the correct parameter value validation. Parse Server throws an error at launch if an invalid value is set for any configuration parameter. 6. Execute `npm run docs` to generate the documentation in the `/out` directory. Take a look at the documentation whether the description and formatting of the newly introduced parameters is satisfactory. From 7ac43a53e6b9b1a85856d1943d35f73fb7104e79 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 7 Apr 2024 00:05:48 +0200 Subject: [PATCH 22/22] removing empty newlines Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/Config.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Config.js b/src/Config.js index 115b387944..5dfd3f9588 100644 --- a/src/Config.js +++ b/src/Config.js @@ -210,11 +210,9 @@ export class Config { static validateSchemaOptions(schema: SchemaOptions) { if (!schema) return; - if (Object.prototype.toString.call(schema) !== '[object Object]') { throw 'Parse Server option schema must be an object.'; } - if (schema.definitions === undefined) { schema.definitions = SchemaOptions.definitions.default; } else if (!Array.isArray(schema.definitions)) { @@ -256,7 +254,6 @@ export class Config { if (Object.prototype.toString.call(pages) !== '[object Object]') { throw 'Parse Server option pages must be an object.'; } - if (pages.enableRouter === undefined) { pages.enableRouter = PagesOptions.enableRouter.default; } else if (!isBoolean(pages.enableRouter)) { @@ -305,7 +302,6 @@ export class Config { } else if (Object.prototype.toString.call(pages.customUrls) !== '[object Object]') { throw 'Parse Server option pages.customUrls must be an object.'; } - if (pages.customRoutes === undefined) { pages.customRoutes = PagesOptions.customRoutes.default; } else if (!(pages.customRoutes instanceof Array)) { @@ -475,7 +471,6 @@ export class Config { } throw e; } - if (fileUpload.enableForAnonymousUser === undefined) { fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { @@ -581,7 +576,6 @@ export class Config { if (databaseOptions == undefined) { return; } - if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') { throw `databaseOptions must be an object`; } @@ -608,13 +602,11 @@ export class Config { ) { throw `rateLimit must be an array or object`; } - const options = Array.isArray(rateLimit) ? rateLimit : [rateLimit]; for (const option of options) { if (Object.prototype.toString.call(option) !== '[object Object]') { throw `rateLimit must be an array of objects`; } - if (option.requestPath == null) { throw `rateLimit.requestPath must be defined`; }